<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="谷粒商城," />










<meta name="description" content="谷粒商城 商城业务商品上架将商品在es中存储 复杂的检索分析，Mysql效率较低，使用ES效果较好。 ES所有数据存在内存中，ES只保存有用的信息（商品信息） SPU代表华为手机及所有手机共享的参数，SKU表示华为手机的具体型号参数化。  方法一：方便检索，占内存  12345678skuId:1skuTitle:华为price: 333attrs:[    &amp;#123;尺寸：5寸&amp;#125;，">
<meta property="og:type" content="article">
<meta property="og:title" content="谷粒商城">
<meta property="og:url" content="https://gitee.com/htt-sub/htt-sub/2023/11/01/%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E/index.html">
<meta property="og:site_name" content="黄宏涛">
<meta property="og:description" content="谷粒商城 商城业务商品上架将商品在es中存储 复杂的检索分析，Mysql效率较低，使用ES效果较好。 ES所有数据存在内存中，ES只保存有用的信息（商品信息） SPU代表华为手机及所有手机共享的参数，SKU表示华为手机的具体型号参数化。  方法一：方便检索，占内存  12345678skuId:1skuTitle:华为price: 333attrs:[    &amp;#123;尺寸：5寸&amp;#125;，">
<meta property="og:locale">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101131111891.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101131433278.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101140921895.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231102184509050.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231102205600353.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103123208688.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103123522255.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103133759257.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103133610939.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103134938358.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103135413465.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105195955638.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108155926418.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108174209780.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108174709342.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175143004.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175348858.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175516621.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175743990.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108180935288.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108183454233.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108193411820.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108193446525.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109093149937.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109101247412.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109125738146.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109145019209.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109153715881.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231111185550463.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231111192114175.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231111214014129.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231116200047885.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231116200125152.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E6%B5%81%E7%A8%8B.jpg">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120091751331.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120104632345.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120105820982.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120151952997.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120165013148.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121090812657.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121103327462.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121103644763.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121105655530.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120145054406.png">
<meta property="og:image" content="c:\Users\ZC\AppData\Roaming\Typora\typora-user-images\image-20231122132817975.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231122134522243.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231122141630012.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231122140858826.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231112210047815.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120135552838.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/solution-1bdadb80e54074aa3088372c17f0244b.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103151519200.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103153853366.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103155633001.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103160303902.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103174644888.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103184805351.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103184930781.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103191443966.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103202327572.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103203716697.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104105251715.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104113206725.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104182219196.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104190134013.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104192318104.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104194105470.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104211353802.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105093817015.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105093849458.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105095139982.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105100816859.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105101647640.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/Untitled.png">
<meta property="og:image" content="c:\Users\ZC\Desktop\Untitled.png">
<meta property="og:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231107094456067.png">
<meta property="article:published_time" content="2023-10-31T16:00:00.000Z">
<meta property="article:modified_time" content="2023-11-22T06:35:45.990Z">
<meta property="article:author" content="黄宏涛">
<meta property="article:tag" content="谷粒商城">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101131111891.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"post","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://gitee.com/htt-sub/htt-sub/2023/11/01/谷粒商城/"/>





  <title>谷粒商城 | 黄宏涛</title>
  








<meta name="generator" content="Hexo 6.3.0"></head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-post page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">黄宏涛</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://gitee.com/htt-sub/htt-sub/2023/11/01/%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.png">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="黄宏涛">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">谷粒商城</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2023-11-01T00:00:00+08:00">
                2023-11-01
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E/" itemprop="url" rel="index">
                    <span itemprop="name">谷粒商城</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h1 id="谷粒商城"><a href="#谷粒商城" class="headerlink" title="谷粒商城"></a>谷粒商城</h1><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101131111891.png" alt="image-20231101131111891"></p>
<h2 id="商城业务"><a href="#商城业务" class="headerlink" title="商城业务"></a>商城业务</h2><h3 id="商品上架"><a href="#商品上架" class="headerlink" title="商品上架"></a>商品上架</h3><h4 id="将商品在es中存储"><a href="#将商品在es中存储" class="headerlink" title="将商品在es中存储"></a>将商品在es中存储</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101131433278.png" alt="image-20231101131433278"></p>
<p><strong>复杂的检索分析，Mysql效率较低</strong>，使用ES效果较好。</p>
<p><strong>ES所有数据存在内存中，ES只保存有用的信息（商品信息）</strong></p>
<p><strong>SPU</strong>代表华为手机及所有手机共享的参数，<strong>SKU</strong>表示华为手机的具体型号参数化。</p>
<blockquote>
<p><strong>方法一：方便检索，占内存</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">skuId:<span class="number">1</span></span><br><span class="line">skuTitle:华为</span><br><span class="line">price: <span class="number">333</span></span><br><span class="line">attrs:[</span><br><span class="line">    &#123;尺寸：<span class="number">5</span>寸&#125;，</span><br><span class="line">    &#123;CPU：高通&#125;，</span><br><span class="line">    &#123;分辨率：全高清&#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<blockquote>
<p>方法二：节省内存，效率低</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">sku索引&#123;</span><br><span class="line">    skuId:<span class="number">1</span></span><br><span class="line">	spuId:<span class="number">11</span></span><br><span class="line">    xxx</span><br><span class="line">&#125;</span><br><span class="line">attr索引&#123;</span><br><span class="line">    spuId:<span class="number">11</span>,</span><br><span class="line">    attrs:[</span><br><span class="line">    &#123;尺寸：<span class="number">5</span>寸&#125;，</span><br><span class="line">    &#123;CPU：高通&#125;，</span><br><span class="line">    &#123;分辨率：全高清&#125;]</span><br><span class="line">&#125;</span><br><span class="line">搜索：小米：粮食、手机、电器</span><br><span class="line"><span class="number">10000</span>个，<span class="number">4000</span>个spu</span><br><span class="line">分步，<span class="number">4000</span>个spu对应的所有可能属性；</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<blockquote>
<p>谷粒商城的ES存储的字段</p>
</blockquote>
<p>查询通过GET product&#x2F;_search</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">PUT product</span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">	<span class="attr">&quot;mappings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">		<span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">			<span class="attr">&quot;skuId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;spuId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span>  <span class="comment">//精确匹配</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;skuTitle&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;text&quot;</span><span class="punctuation">,</span>  <span class="comment">//全文匹配，分词，指定分词器为ik_smart</span></span><br><span class="line">				<span class="attr">&quot;analyzer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ik_smart&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;skuPrice&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;skuImg&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span> <span class="comment">//表示不能被检索</span></span><br><span class="line">				<span class="attr">&quot;doc_values&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span>  <span class="comment">//表示不可以做排序、聚合，节省空间</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;saleCount&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;hasStock&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;boolean&quot;</span>  <span class="comment">//表示是否有存储，不用频繁的去更新是否有库存</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;hotScore&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;brandId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;catalogId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;brandName&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;doc_values&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;brandImg&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;doc_values&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;catalogName&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">				<span class="attr">&quot;doc_values&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">			<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;attrs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">				<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;nested&quot;</span><span class="punctuation">,</span>   <span class="comment">//nested嵌入式</span></span><br><span class="line">				<span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">					<span class="attr">&quot;attrId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">						<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;long&quot;</span></span><br><span class="line">					<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">					<span class="attr">&quot;attrName&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">						<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span><span class="punctuation">,</span></span><br><span class="line">						<span class="attr">&quot;index&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">						<span class="attr">&quot;doc_values&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">					<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">					<span class="attr">&quot;attrValue&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">						<span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keyword&quot;</span></span><br><span class="line">					<span class="punctuation">&#125;</span></span><br><span class="line">				<span class="punctuation">&#125;</span></span><br><span class="line">			<span class="punctuation">&#125;</span></span><br><span class="line">		<span class="punctuation">&#125;</span></span><br><span class="line">	<span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>nested数据类型</strong></p>
</blockquote>
<p>商品的属性一定要是<strong>嵌入式属性</strong>。否则会扁平化处理。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231101140921895.png" alt="image-20231101140921895"></p>
<blockquote>
<p><strong>检索当前sku的所有可以被用来检索的规格属性</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//TODO 检索当前sku的所有可以被用来检索的规格属性attrs</span></span><br><span class="line">        List&lt;ProductAttrValueEntity&gt; baseAttrs = attrValueService.baseAttrlistforspu(spuId);</span><br><span class="line">        <span class="comment">// 获取基本属性attr_id列表</span></span><br><span class="line">        List&lt;Long&gt; attrIds = baseAttrs.stream().map(attr -&gt; attr.getAttrId()).collect(Collectors.toList());</span><br><span class="line">        <span class="comment">// 获取可用于检索的属性id列表</span></span><br><span class="line">        List&lt;Long&gt; searchAttrIds = attrService.selectSearchAttrs(attrIds);</span><br><span class="line">        Set&lt;Long&gt; idSet=<span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;(searchAttrIds);</span><br><span class="line">        <span class="comment">// 从过滤掉基本属性过滤出可用于检索的属性id列表，并将数组类型转为Attrs</span></span><br><span class="line">        List&lt;SkuEsModel.Attrs&gt; attrValueEntities = baseAttrs.</span><br><span class="line">                stream().</span><br><span class="line">                <span class="comment">// filter返回true表示不过滤</span></span><br><span class="line">                filter(item -&gt; idSet.contains(item.getAttrId())).</span><br><span class="line">                <span class="comment">// 将数据转为Attrs类型</span></span><br><span class="line">                map(item-&gt;&#123;</span><br><span class="line">                    SkuEsModel.<span class="type">Attrs</span> <span class="variable">attrs</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SkuEsModel</span>.Attrs();</span><br><span class="line">                    BeanUtils.copyProperties(item,attrs);</span><br><span class="line">                    <span class="keyword">return</span> attrs;</span><br><span class="line">                &#125;).collect(Collectors.toList());</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>远程查询库存</strong></p>
</blockquote>
<p><span style="color:red;font-weight:bold">stream().collect(Collectors.toMap())可以将数据收集转为Map</span></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//远程库存查询,一次查询所有的sku库存信息</span></span><br><span class="line">        Map&lt;Long, Boolean&gt; stockMap =<span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> wareFeginService.skuWare(skuIds);</span><br><span class="line">            <span class="comment">// 将SkuHasStockVo对象转为Map</span></span><br><span class="line">            stockMap = r.getData(<span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;List&lt;SkuHasStockVo&gt;&gt;()&#123;&#125;).</span><br><span class="line">                    stream().</span><br><span class="line">                    collect(Collectors.toMap(SkuHasStockVo::getSkuId,</span><br><span class="line">                            item -&gt; item.isHasStock()));</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            log.error(<span class="string">&quot;库存服务查询异常，原因&#123;&#125;&quot;</span>,e);</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>gulimall-ware模块的库存查询方法</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//根据skuid查询是否有库存</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/hasstock&quot;)</span></span><br><span class="line"><span class="keyword">public</span> R <span class="title function_">skuWare</span><span class="params">(<span class="meta">@RequestBody</span> List&lt;Long&gt; skuIds)</span>&#123;</span><br><span class="line">    List&lt;SkuHasStockVo&gt; hasStock = wareSkuService.skuWare(skuIds);</span><br><span class="line">    <span class="keyword">return</span> R.ok().setData(hasStock);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> List&lt;SkuHasStockVo&gt; <span class="title function_">skuWare</span><span class="params">(List&lt;Long&gt; skuIds)</span> &#123;</span><br><span class="line">    <span class="comment">//库存要-锁定的库存（有人已经下单，但没支付）</span></span><br><span class="line">    List&lt;SkuHasStockVo&gt; collect = skuIds.stream().map(item -&gt; &#123;</span><br><span class="line">        <span class="comment">// 查询当前sku的总库存量</span></span><br><span class="line">        <span class="comment">// select sum(stock-stock_locked) from wms_ware where sku_id=1</span></span><br><span class="line">        <span class="comment">//long要写包装类型，可以允许有null出现</span></span><br><span class="line">        <span class="type">Long</span> <span class="variable">count</span> <span class="operator">=</span> baseMapper.getSkuStock(item);</span><br><span class="line">        <span class="type">SkuHasStockVo</span> <span class="variable">skuHasStockVo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SkuHasStockVo</span>();</span><br><span class="line">        skuHasStockVo.setSkuId(item);</span><br><span class="line">        skuHasStockVo.setHasStock(count==<span class="literal">null</span>?<span class="literal">false</span>:count &gt; <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">return</span> skuHasStockVo;</span><br><span class="line">    &#125;).collect(Collectors.toList());</span><br><span class="line">    <span class="comment">// 返回库存状态列表</span></span><br><span class="line">    <span class="keyword">return</span> collect;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="修改R对象，能够自动逆转数据类型"><a href="#修改R对象，能够自动逆转数据类型" class="headerlink" title="修改R对象，能够自动逆转数据类型"></a>修改R对象，能够自动逆转数据类型</h4><blockquote>
<p>R</p>
</blockquote>
<p><strong>TypeReference</strong>用于复杂对象的传输</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 利用fastjson进行逆转</span></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getData</span><span class="params">(TypeReference&lt;T&gt; typeReference)</span>&#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">data</span> <span class="operator">=</span> get(<span class="string">&quot;data&quot;</span>); <span class="comment">//默认是map类型</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> JSON.toJSONString(data);</span><br><span class="line">    <span class="type">T</span> <span class="variable">t</span> <span class="operator">=</span> JSON.parseObject(s, typeReference);</span><br><span class="line">    <span class="keyword">return</span> t;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> R <span class="title function_">setData</span><span class="params">(Object object)</span>&#123;</span><br><span class="line">    put(<span class="string">&quot;data&quot;</span>,object);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="封装SKU信息"><a href="#封装SKU信息" class="headerlink" title="封装SKU信息"></a><strong>封装SKU信息</strong></h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//TODO 2. 封装每个SKU的信息</span></span><br><span class="line">        Map&lt;Long, Boolean&gt; finalStockMap = stockMap;</span><br><span class="line"></span><br><span class="line">        List&lt;SkuEsModel&gt; upProducts = skuInfoEntityList.stream().map(skuInfoEntity -&gt; &#123;</span><br><span class="line">            <span class="type">SkuEsModel</span> <span class="variable">skuEsModel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SkuEsModel</span>();</span><br><span class="line">            <span class="comment">//属性拷贝</span></span><br><span class="line">            BeanUtils.copyProperties(skuInfoEntity, skuEsModel);</span><br><span class="line">            <span class="comment">//skuPrice、skuImg、hasStock、</span></span><br><span class="line">            skuEsModel.setSkuPrice(skuInfoEntity.getPrice());</span><br><span class="line">            skuEsModel.setSkuImg(skuInfoEntity.getSkuDefaultImg());</span><br><span class="line">            <span class="comment">/*//TODO 1. 远程调用hasStock库存查询</span></span><br><span class="line"><span class="comment">            每次遍历进行一次远程调用，效率较低，可以一次查询所有的sku的库存状态,放在遍历之前</span></span><br><span class="line"><span class="comment">            &#125;*/</span></span><br><span class="line">            <span class="keyword">if</span> (finalStockMap == <span class="literal">null</span>) &#123;</span><br><span class="line">                skuEsModel.setHasStock(<span class="literal">false</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                skuEsModel.setHasStock(finalStockMap.get(skuInfoEntity.getSkuId()));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//TODO 2.热度评分hotScore,默认为0</span></span><br><span class="line">            skuEsModel.setHotScore(<span class="number">0L</span>);</span><br><span class="line">            <span class="comment">//TODO 3.brandName、brandImg、</span></span><br><span class="line">            <span class="type">BrandEntity</span> <span class="variable">brandEntity</span> <span class="operator">=</span> brandService.getById(skuInfoEntity.getBrandId());</span><br><span class="line">            skuEsModel.setBrandName(brandEntity.getName());</span><br><span class="line">            skuEsModel.setBrandImg(brandEntity.getLogo());</span><br><span class="line">            <span class="comment">//TODO 4.catalogName</span></span><br><span class="line">            <span class="type">CategoryEntity</span> <span class="variable">categoryServiceById</span> <span class="operator">=</span> categoryService.getById(skuInfoEntity.getCatalogId());</span><br><span class="line">            skuEsModel.setCatalogName(categoryServiceById.getName());</span><br><span class="line">            <span class="comment">// 设置检索属性</span></span><br><span class="line">            skuEsModel.setAttrs(attrValueEntities);</span><br><span class="line">            <span class="keyword">return</span> skuEsModel;</span><br><span class="line">        &#125;).collect(Collectors.toList());</span><br></pre></td></tr></table></figure>

<h4 id="向ES发送数据"><a href="#向ES发送数据" class="headerlink" title="向ES发送数据"></a><strong>向ES发送数据</strong></h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//TODO 5. 将数据发送到ES中</span></span><br><span class="line"><span class="type">R</span> <span class="variable">r</span> <span class="operator">=</span> searchFeignService.productStatusUp(upProducts);</span><br><span class="line"><span class="keyword">if</span> (r.getCode()==<span class="number">0</span>)&#123;</span><br><span class="line">    <span class="comment">//远程调用成功，修改sku上架状态</span></span><br><span class="line">    <span class="type">SpuInfoEntity</span> <span class="variable">spuInfoEntity</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SpuInfoEntity</span>();</span><br><span class="line">    spuInfoEntity.setUpdateTime(<span class="keyword">new</span> <span class="title class_">Date</span>());</span><br><span class="line">    spuInfoEntity.setPublishStatus(<span class="number">1</span>);</span><br><span class="line">    spuInfoEntity.setId(spuId);</span><br><span class="line">    updateById(spuInfoEntity);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    <span class="comment">//远程调用失败</span></span><br><span class="line">    <span class="comment">//TODO 重复调用、接口幂等性、重试机制</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>gulimall-search检索模块</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//上架商品</span></span><br><span class="line"><span class="meta">@PostMapping(&quot;/product&quot;)</span></span><br><span class="line"><span class="keyword">public</span> R <span class="title function_">productStatusUp</span><span class="params">(<span class="meta">@RequestBody</span> List&lt;SkuEsModel&gt; skuEsModels)</span>&#123;</span><br><span class="line">    System.out.println(skuEsModels);</span><br><span class="line">    <span class="type">boolean</span> b=<span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        b=productSaveService.productStatusUp(skuEsModels);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;ElasticSaveController商品上架错误&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),</span><br><span class="line">                BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg() );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!b)&#123;</span><br><span class="line">        <span class="keyword">return</span> R.ok();</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),</span><br><span class="line">                BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg() );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>productStatusUp方法</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Service(&quot;productSaveService&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductSaveServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">ProductSaveService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RestHighLevelClient esRestClient;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">productStatusUp</span><span class="params">(List&lt;SkuEsModel&gt; skuEsModels)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1.在es中建立索引，建立号映射关系（doc/json/product-mapping.json）</span></span><br><span class="line">        <span class="comment">//需要单独去运行，设置索引</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">//2. 在ES中保存这些数据</span></span><br><span class="line">        <span class="type">BulkRequest</span> <span class="variable">bulkRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BulkRequest</span>();</span><br><span class="line">        <span class="keyword">for</span> (SkuEsModel skuEsModel : skuEsModels) &#123;</span><br><span class="line">            <span class="comment">//构造保存请求</span></span><br><span class="line">            <span class="type">IndexRequest</span> <span class="variable">indexRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IndexRequest</span>(EsConstant.PRODUCT_INDEX);</span><br><span class="line">            indexRequest.id(skuEsModel.getSkuId().toString());</span><br><span class="line">            <span class="type">String</span> <span class="variable">jsonString</span> <span class="operator">=</span> JSON.toJSONString(skuEsModel);</span><br><span class="line">            indexRequest.source(jsonString, XContentType.JSON);</span><br><span class="line">            bulkRequest.add(indexRequest);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">BulkResponse</span> <span class="variable">bulk</span> <span class="operator">=</span> esRestClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//TODO 如果批量错误</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">hasFailures</span> <span class="operator">=</span> bulk.hasFailures();</span><br><span class="line"></span><br><span class="line">        List&lt;String&gt; collect = Arrays.asList(bulk.getItems()).stream().map(item -&gt; &#123;</span><br><span class="line">            <span class="keyword">return</span> item.getId();</span><br><span class="line">        &#125;).collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line">        log.info(<span class="string">&quot;商品上架完成：&#123;&#125;&quot;</span>,collect);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> hasFailures;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="首页-三级分类数据"><a href="#首页-三级分类数据" class="headerlink" title="首页-三级分类数据"></a>首页-三级分类数据</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231102184509050.png" alt="image-20231102184509050"></p>
<p><strong>图片、js等静态资源放到nginx中，需要处理的资源放到微服务中</strong>。</p>
<blockquote>
<p>Thymeleaf</p>
</blockquote>
<p>依赖</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">thymeleaf:</span><br><span class="line">  cache: false   //关闭缓存</span><br></pre></td></tr></table></figure>

<p><strong>静态资源未生效，添加一个配置类</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebMvcConfig</span> <span class="keyword">extends</span> <span class="title class_">WebMvcConfigurerAdapter</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addResourceHandlers</span><span class="params">(ResourceHandlerRegistry registry)</span> &#123;</span><br><span class="line">        registry.addResourceHandler(<span class="string">&quot;/static/**&quot;</span>).addResourceLocations(<span class="string">&quot;classpath:/static/&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>热部署</p>
</blockquote>
<p>添加dev-tools</p>
<p>修改完页面shift + f9自动编译页面</p>
<blockquote>
<p><strong>首页的三级分类展示</strong></p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231102205600353.png" alt="image-20231102205600353"></p>
<blockquote>
<p><strong>封装数据对象</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Category2Vo</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String catalog1Id;</span><br><span class="line">    <span class="keyword">private</span> List&lt;Category3Vo&gt; catalog3List;</span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@AllArgsConstructor</span></span><br><span class="line">    <span class="meta">@NoArgsConstructor</span></span><br><span class="line">    <span class="meta">@Data</span></span><br><span class="line">    <span class="comment">//静态内部类，三级分类</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Category3Vo</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> String catalog2Id;</span><br><span class="line">        <span class="keyword">private</span> String id;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>请求</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/index/json/catalog.json&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJson</span><span class="params">()</span>&#123;</span><br><span class="line">    Map&lt;String,List&lt;Category2Vo&gt;&gt; category2VoList=categoryService.getCatelogJson();</span><br><span class="line">    <span class="keyword">return</span> category2VoList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>查询方法</p>
</blockquote>
<p><strong>代码中给对象setter值，可以直接用构造方法初始化</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 返回二级分类及三级分类的数据</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJson</span><span class="params">()</span> &#123;</span><br><span class="line">    Map&lt;String,List&lt;Category2Vo&gt;&gt; map=<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    List&lt;CategoryEntity&gt; level1Category = getLevel1Category();</span><br><span class="line">    level1Category.forEach(level1-&gt;&#123;</span><br><span class="line">        <span class="comment">//根据一级分类获取二级分类</span></span><br><span class="line">        List&lt;CategoryEntity&gt; level2 = list(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;CategoryEntity&gt;().eq(<span class="string">&quot;parent_cid&quot;</span>, level1.getCatId()));</span><br><span class="line">        List&lt;Category2Vo&gt; level2List=<span class="literal">null</span>;</span><br><span class="line">        <span class="comment">//当前二级分类要存在，才继续查询</span></span><br><span class="line">        <span class="keyword">if</span> (level2!=<span class="literal">null</span>)&#123;</span><br><span class="line">            level2List = level2.stream().map(level2Category -&gt; &#123;</span><br><span class="line">                <span class="comment">// 当前二级分类信息</span></span><br><span class="line">                <span class="type">CategoryEntity</span> <span class="variable">byId</span> <span class="operator">=</span> getById(level2Category);</span><br><span class="line">                <span class="comment">// 当前二级分类对应的三级分类信息</span></span><br><span class="line">                List&lt;CategoryEntity&gt; level3CategoryList = list(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;CategoryEntity&gt;().eq(<span class="string">&quot;parent_cid&quot;</span>, byId.getCatId()));</span><br><span class="line">                List&lt;Category2Vo.Category3Vo&gt; level3List =<span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">if</span> (level3CategoryList!=<span class="literal">null</span>)&#123;</span><br><span class="line">                    level3List = level3CategoryList.stream().map(level3 -&gt; &#123;</span><br><span class="line">                        <span class="type">CategoryEntity</span> <span class="variable">byId1</span> <span class="operator">=</span> getById(level3);</span><br><span class="line">                        Category2Vo.<span class="type">Category3Vo</span> <span class="variable">category3Vo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Category2Vo</span>.Category3Vo(</span><br><span class="line">                                byId1.getParentCid().toString(),</span><br><span class="line">                                byId1.getCatId().toString(),</span><br><span class="line">                                byId1.getName()</span><br><span class="line">                        );</span><br><span class="line">                        <span class="keyword">return</span> category3Vo;</span><br><span class="line">                    &#125;).collect(Collectors.toList());</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="type">Category2Vo</span> <span class="variable">category2Vo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Category2Vo</span>(</span><br><span class="line">                        String.valueOf(level1.getCatId()),</span><br><span class="line">                        level3List,</span><br><span class="line">                        byId.getCatId().toString(),</span><br><span class="line">                        byId.getName()</span><br><span class="line">                        );</span><br><span class="line">                <span class="keyword">return</span> category2Vo;</span><br><span class="line">            &#125;).collect(Collectors.toList());</span><br><span class="line">        &#125;</span><br><span class="line">       map.put(String.valueOf(level1.getCatId()),level2List);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> map;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后的Map也可以用<strong>stream流通过toMap实现转换实</strong>现，和toList类似</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">level1Category.stream().map(</span><br><span class="line">      .......</span><br><span class="line">    .......</span><br><span class="line">).collection(Collectors.toMap(v-&gt;&#123;</span><br><span class="line">    v.getCatId()&#125;,k-&gt;&#123;</span><br><span class="line">    level2List</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure>

<p>public Map&lt;String, List<Catelog2Vo>&gt; getCatalogJson() {<br>        System.out.println(“查询了数据库”);</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">    //将数据库的多次查询变为一次</span><br><span class="line">    List&lt;CategoryEntity&gt; selectList = this.baseMapper.selectList(null);</span><br><span class="line"></span><br><span class="line">    //1、查出所有分类</span><br><span class="line">    //1、1）查出所有一级分类</span><br><span class="line">    List&lt;CategoryEntity&gt; level1Categorys = getParent_cid(selectList, 0L);</span><br><span class="line"></span><br><span class="line">    //封装数据</span><br><span class="line">    Map&lt;String, List&lt;Catelog2Vo&gt;&gt; parentCid = level1Categorys.stream().collect(Collectors.toMap(k -&gt; k.getCatId().toString(), v -&gt; &#123;</span><br><span class="line">        //1、每一个的一级分类,查到这个一级分类的二级分类</span><br><span class="line">        List&lt;CategoryEntity&gt; categoryEntities = getParent_cid(selectList, v.getCatId());</span><br><span class="line"></span><br><span class="line">        //2、封装上面的结果</span><br><span class="line">        List&lt;Catelog2Vo&gt; catelog2Vos = null;</span><br><span class="line">        if (categoryEntities != null) &#123;</span><br><span class="line">            catelog2Vos = categoryEntities.stream().map(l2 -&gt; &#123;</span><br><span class="line">                Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName().toString());</span><br><span class="line"></span><br><span class="line">                //1、找当前二级分类的三级分类封装成vo</span><br><span class="line">                List&lt;CategoryEntity&gt; level3Catelog = getParent_cid(selectList, l2.getCatId());</span><br><span class="line"></span><br><span class="line">                if (level3Catelog != null) &#123;</span><br><span class="line">                    List&lt;Catelog2Vo.Category3Vo&gt; category3Vos = level3Catelog.stream().map(l3 -&gt; &#123;</span><br><span class="line">                        //2、封装成指定格式</span><br><span class="line">                        Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());</span><br><span class="line"></span><br><span class="line">                        return category3Vo;</span><br><span class="line">                    &#125;).collect(Collectors.toList());</span><br><span class="line">                    catelog2Vo.setCatalog3List(category3Vos);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                return catelog2Vo;</span><br><span class="line">            &#125;).collect(Collectors.toList());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return catelog2Vos;</span><br><span class="line">    &#125;));</span><br><span class="line"></span><br><span class="line">    return parentCid;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>官方用的Map方法，减少了嵌套</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Map&lt;String, List&lt;Catelog2Vo&gt;&gt; <span class="title function_">getCatalogJson</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;查询了数据库&quot;</span>);    </span><br><span class="line"><span class="comment">//将数据库的多次查询变为一次</span></span><br><span class="line">    List&lt;CategoryEntity&gt; selectList = <span class="built_in">this</span>.baseMapper.selectList(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//1、查出所有分类</span></span><br><span class="line">    <span class="comment">//1、1）查出所有一级分类</span></span><br><span class="line">    List&lt;CategoryEntity&gt; level1Categorys = getParent_cid(selectList, <span class="number">0L</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//封装数据</span></span><br><span class="line">    Map&lt;String, List&lt;Catelog2Vo&gt;&gt; parentCid = level1Categorys.stream().collect(Collectors.toMap(k -&gt; k.getCatId().toString(), v -&gt; &#123;</span><br><span class="line">        <span class="comment">//1、每一个的一级分类,查到这个一级分类的二级分类</span></span><br><span class="line">        List&lt;CategoryEntity&gt; categoryEntities = getParent_cid(selectList, v.getCatId());</span><br><span class="line"></span><br><span class="line">        <span class="comment">//2、封装上面的结果</span></span><br><span class="line">        List&lt;Catelog2Vo&gt; catelog2Vos = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (categoryEntities != <span class="literal">null</span>) &#123;</span><br><span class="line">            catelog2Vos = categoryEntities.stream().map(l2 -&gt; &#123;</span><br><span class="line">                <span class="type">Catelog2Vo</span> <span class="variable">catelog2Vo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Catelog2Vo</span>(v.getCatId().toString(), <span class="literal">null</span>, l2.getCatId().toString(), l2.getName().toString());</span><br><span class="line"></span><br><span class="line">                <span class="comment">//1、找当前二级分类的三级分类封装成vo</span></span><br><span class="line">                List&lt;CategoryEntity&gt; level3Catelog = getParent_cid(selectList, l2.getCatId());</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (level3Catelog != <span class="literal">null</span>) &#123;</span><br><span class="line">                    List&lt;Catelog2Vo.Category3Vo&gt; category3Vos = level3Catelog.stream().map(l3 -&gt; &#123;</span><br><span class="line">                        <span class="comment">//2、封装成指定格式</span></span><br><span class="line">                        Catelog2Vo.<span class="type">Category3Vo</span> <span class="variable">category3Vo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Catelog2Vo</span>.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">return</span> category3Vo;</span><br><span class="line">                    &#125;).collect(Collectors.toList());</span><br><span class="line">                    catelog2Vo.setCatalog3List(category3Vos);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">return</span> catelog2Vo;</span><br><span class="line">            &#125;).collect(Collectors.toList());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> catelog2Vos;</span><br><span class="line">    &#125;));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> parentCid;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> List&lt;CategoryEntity&gt; <span class="title function_">getParent_cid</span><span class="params">(List&lt;CategoryEntity&gt; selectList,Long parentCid)</span> &#123;</span><br><span class="line">        List&lt;CategoryEntity&gt; categoryEntities = selectList.stream().filter(item -&gt; item.getParentCid().equals(parentCid)).collect(Collectors.toList());</span><br><span class="line">        <span class="keyword">return</span> categoryEntities;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<h3 id="nginx搭建域名访问环境"><a href="#nginx搭建域名访问环境" class="headerlink" title="nginx搭建域名访问环境"></a>nginx搭建域名访问环境</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103123208688.png" alt="image-20231103123208688"></p>
<p><strong>反向代理：</strong>屏蔽内网服务器信息，负载均衡访问</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103123522255.png" alt="image-20231103123522255"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103133759257.png" alt="image-20231103133759257"></p>
<p><strong>添加网关</strong>：将gulimall.com通过nginx转发到网关</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103133610939.png" alt="image-20231103133610939"></p>
<blockquote>
<p>gulimall-gateway配置路由</p>
</blockquote>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">一定要放在配置最后面</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">id:</span> <span class="string">gulimall_host_route</span></span><br><span class="line">  <span class="attr">uri:</span> <span class="string">lb://gulimall-product</span></span><br><span class="line">  <span class="attr">predicates:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">Host=**.gulimall.com</span></span><br></pre></td></tr></table></figure>

<p>nginx代理给网关的时候会丢失请求的Host信息，添加设置</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103134938358.png" alt="image-20231103134938358"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103135413465.png" alt="image-20231103135413465"></p>
<h3 id="检索服务"><a href="#检索服务" class="headerlink" title="检索服务"></a>检索服务</h3><p>将静态文件上传到Nginx中，添加网关转发请求</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">- id: gulimall_search_route</span><br><span class="line">  uri: lb://gulimall-search</span><br><span class="line">  predicates:</span><br><span class="line">    - Host=search.gulimall.com</span><br></pre></td></tr></table></figure>





<h4 id="检索参数"><a href="#检索参数" class="headerlink" title="检索参数"></a>检索参数</h4><p>三种方法：</p>
<ol>
<li>输入框查询</li>
<li>分类查询</li>
<li>选择筛选条件进行查询，如指定价格区间</li>
</ol>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105195955638.png" alt="image-20231105195955638"></p>
<h4 id="请求参数封装"><a href="#请求参数封装" class="headerlink" title="请求参数封装"></a>请求参数封装</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SearchParamVo</span> &#123;</span><br><span class="line">    <span class="comment">// 全文匹配</span></span><br><span class="line">    <span class="keyword">private</span> String keyword;</span><br><span class="line">    <span class="keyword">private</span> Long catalog3Id;</span><br><span class="line">    <span class="comment">// 排序条件 销量、热度分、价格</span></span><br><span class="line">    <span class="keyword">private</span> String sort;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 更多的过滤条件</span></span><br><span class="line"><span class="comment">     * hasStack是否有货，价格区间、brandId、attrs</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer hasStock; <span class="comment">//是否有货</span></span><br><span class="line">    <span class="keyword">private</span> String skuPrice; <span class="comment">//价格区间</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;Long&gt; brandId; <span class="comment">//品牌,可以同时选多个</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 多个值，可以用冒号分隔</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; attts; <span class="comment">//按照属性查询</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 数据多，可能要分页</span></span><br><span class="line">    <span class="keyword">private</span> Integer pageNum; <span class="comment">//页码</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="响应参数封装"><a href="#响应参数封装" class="headerlink" title="响应参数封装"></a>响应参数封装</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 返回给页面的信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SearchResultVo</span> &#123;</span><br><span class="line">    <span class="comment">// 所有商品信息</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;SkuEsModel&gt; products;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分页信息</span></span><br><span class="line">    <span class="keyword">private</span> Integer pageNum;<span class="comment">// 当前页码</span></span><br><span class="line">    <span class="keyword">private</span> Long total;<span class="comment">// 总记录数</span></span><br><span class="line">    <span class="keyword">private</span> Integer totalPages; <span class="comment">//总页码</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> List&lt;BrandVo&gt; brands; <span class="comment">//当前查询涉及到品牌</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;AttrVo&gt; attrs;<span class="comment">// 当前查询结果涉及到的所有属性</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;CatalogVo&gt; catalogs;<span class="comment">// 结果涉及到的所有分类信息</span></span><br><span class="line">    <span class="meta">@Data</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">BrandVo</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> Long brandId;</span><br><span class="line">        <span class="keyword">private</span> String brandName;</span><br><span class="line">        <span class="keyword">private</span> String brandImg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Data</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">AttrVo</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> Long attrId;</span><br><span class="line">        <span class="keyword">private</span> String attrName;</span><br><span class="line">        <span class="keyword">private</span> List&lt;String&gt; attrValue;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Data</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">CatalogVo</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> Long catalogId;</span><br><span class="line">        <span class="keyword">private</span> String catalogName;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="检索DSL测试–ES查询"><a href="#检索DSL测试–ES查询" class="headerlink" title="检索DSL测试–ES查询"></a>检索DSL测试–ES查询</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 模糊匹配、过滤（按照属性、分类、品牌、价格区间、库存），排序，分页</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;must&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;match&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;skuTitle&quot;</span><span class="punctuation">:</span><span class="string">&quot;HUAWEI&quot;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;filter&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;catalogId&quot;</span><span class="punctuation">:</span><span class="number">225</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;brandId&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="number">9</span><span class="punctuation">]</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span>  <span class="comment">// ES 把数据扁平化处理，需要nested标签，获取原来数组中内容</span></span><br><span class="line">                    <span class="attr">&quot;nested&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span><span class="string">&quot;attrs&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;bool&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                                <span class="attr">&quot;must&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">                                    <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                                        <span class="attr">&quot;attrs.attrId&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                                            <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span><span class="string">&quot;15&quot;</span></span><br><span class="line">                                        <span class="punctuation">&#125;</span></span><br><span class="line">                                    <span class="punctuation">&#125;</span></span><br><span class="line">                                <span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span></span><br><span class="line">                                    <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                                        <span class="attr">&quot;attrs.attrValue&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">                                            <span class="string">&quot;以官网信息为准&quot;</span></span><br><span class="line">                                        <span class="punctuation">]</span></span><br><span class="line">                                    <span class="punctuation">&#125;</span></span><br><span class="line">                                <span class="punctuation">&#125;</span></span><br><span class="line">                                <span class="punctuation">]</span></span><br><span class="line">                            <span class="punctuation">&#125;</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;term&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;hasStock&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span><span class="string">&quot;false&quot;</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;range&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;skuPrice&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;gte&quot;</span><span class="punctuation">:</span><span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">                            <span class="attr">&quot;lte&quot;</span><span class="punctuation">:</span><span class="number">13000</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;sort&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;skuPrice&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;order&quot;</span><span class="punctuation">:</span><span class="string">&quot;desc&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 分页查询</span></span><br><span class="line">    <span class="attr">&quot;from&quot;</span><span class="punctuation">:</span><span class="number">0</span><span class="punctuation">,</span>   <span class="comment">// 当前页</span></span><br><span class="line">    <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">2</span>    <span class="comment">// 页面大小</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h4 id="检索DSL聚合分析"><a href="#检索DSL聚合分析" class="headerlink" title="检索DSL聚合分析"></a>检索DSL聚合分析</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;match_all&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span>  <span class="comment">// 将上面这部分去掉，把aggs添加到上面分页查询的后面，就构成了完成的语句</span></span><br><span class="line">    <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;brang_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="comment">// 聚合出了品牌ID</span></span><br><span class="line">                <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;brandId&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="comment">// 商标聚合内置了商标名的子聚合</span></span><br><span class="line">                <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;brand_name_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;brandName&quot;</span><span class="punctuation">,</span></span><br><span class="line">                            <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;brand_img_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;brandImg&quot;</span><span class="punctuation">,</span></span><br><span class="line">                            <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;catelog_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;catalogId&quot;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span><span class="comment">// 看每一个分类的子聚合是什么</span></span><br><span class="line">            <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;catalog_name_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;catalogName&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;attr_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">            <span class="comment">// 如果是嵌入式属性，查询聚合、分析都应用nested</span></span><br><span class="line">            <span class="attr">&quot;nested&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;path&quot;</span><span class="punctuation">:</span><span class="string">&quot;attrs&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;attr_id_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;attrs.attrId&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">                    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;aggs&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;attr_name_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;attrs.attrName&quot;</span><span class="punctuation">,</span></span><br><span class="line">                            <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;attr_value_agg&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                        <span class="attr">&quot;terms&quot;</span><span class="punctuation">:</span><span class="punctuation">&#123;</span></span><br><span class="line">                            <span class="attr">&quot;field&quot;</span><span class="punctuation">:</span><span class="string">&quot;attrs.attrValue&quot;</span><span class="punctuation">,</span></span><br><span class="line">                            <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span><span class="number">10</span></span><br><span class="line">                        <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                    <span class="punctuation">&#125;</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">   </span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<h4 id="SearchRequest检索请求"><a href="#SearchRequest检索请求" class="headerlink" title="SearchRequest检索请求"></a>SearchRequest检索请求</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 准备检索请求</span></span><br><span class="line"><span class="keyword">private</span> SearchRequest <span class="title function_">buildSearchRequest</span><span class="params">(SearchParamVo param)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">SearchSourceBuilder</span> <span class="variable">sourceBuilder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchSourceBuilder</span>();<span class="comment">// 构建DSL语句</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 模糊匹配，过滤（按照属性、分裂、品牌、价格区间、库存）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="comment">// 1.构建bool ----query</span></span><br><span class="line">    <span class="type">BoolQueryBuilder</span> <span class="variable">boolQuery</span> <span class="operator">=</span> QueryBuilders.boolQuery();</span><br><span class="line">    <span class="comment">// 1.1 must-模糊匹配</span></span><br><span class="line">    <span class="keyword">if</span> (!StringUtils.isEmpty(param.getKeyword()))&#123;</span><br><span class="line">        boolQuery.must(QueryBuilders.matchQuery(<span class="string">&quot;skuTitle&quot;</span>,param.getKeyword()));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 1.2 bool-filter-三级分类</span></span><br><span class="line">    <span class="keyword">if</span> (param.getCatalog3Id()!=<span class="literal">null</span>)&#123;</span><br><span class="line">        boolQuery.filter(QueryBuilders.termQuery(<span class="string">&quot;catalogId&quot;</span>,param.getCatalog3Id()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 1.3 bool-filter-品牌ID</span></span><br><span class="line">    <span class="keyword">if</span> (param.getBrandId()!=<span class="literal">null</span>&amp;&amp;param.getBrandId().size()&gt;<span class="number">0</span>)&#123;</span><br><span class="line">        boolQuery.filter(QueryBuilders.termQuery(<span class="string">&quot;brandId&quot;</span>,param.getBrandId()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 1.4 bool-filter-按照指定属性进行查询</span></span><br><span class="line">    <span class="comment">// attrs=1_5寸：8寸&amp;attrs=2_16G:8G</span></span><br><span class="line">    <span class="keyword">if</span> (param.getAttts()!=<span class="literal">null</span>&amp;&amp;param.getAttts().size()&gt;<span class="number">0</span>)&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (String attr : param.getAttts()) &#123;</span><br><span class="line">            <span class="type">BoolQueryBuilder</span> <span class="variable">nestedBoolQuery</span> <span class="operator">=</span> QueryBuilders.boolQuery();</span><br><span class="line">            <span class="comment">// attr=1_5寸:8寸</span></span><br><span class="line">            String[] s = attr.split(<span class="string">&quot;_&quot;</span>);</span><br><span class="line">            String attrId=s[<span class="number">0</span>]; <span class="comment">// 属性ID</span></span><br><span class="line">            String[] attrValues = s[<span class="number">1</span>].split(<span class="string">&quot;:&quot;</span>);<span class="comment">// 属性值</span></span><br><span class="line">            nestedBoolQuery.must(QueryBuilders.termQuery(<span class="string">&quot;attrs.attrId&quot;</span>,attrId));</span><br><span class="line">            nestedBoolQuery.must(QueryBuilders.termsQuery(<span class="string">&quot;attrs.attrValue&quot;</span>,attrValues));</span><br><span class="line">            <span class="comment">// TODO 每一个都必须生成一个nested</span></span><br><span class="line">            <span class="type">NestedQueryBuilder</span> <span class="variable">nestedQueryBuilder</span> <span class="operator">=</span> QueryBuilders.nestedQuery(<span class="string">&quot;attrs&quot;</span>,nestedBoolQuery, ScoreMode.None);</span><br><span class="line">            boolQuery.filter(nestedQueryBuilder);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 1.5 bool-filter-库存</span></span><br><span class="line">    boolQuery.filter(QueryBuilders.termQuery(<span class="string">&quot;hasStock&quot;</span>,param.getHasStock()==<span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 1.5 bool-filter-价格区间，页面传入的数据为1_500、_500、500_</span></span><br><span class="line">    <span class="keyword">if</span> (!StringUtils.isEmpty(param.getSkuPrice()))&#123;</span><br><span class="line">        String price= param.getSkuPrice();</span><br><span class="line">        String[] priceRange= price.split(<span class="string">&quot;_&quot;</span>);</span><br><span class="line">        String startPrice=priceRange[<span class="number">0</span>];</span><br><span class="line">        String endPrice=priceRange[<span class="number">1</span>];</span><br><span class="line">        <span class="type">RangeQueryBuilder</span> <span class="variable">rangeQuery</span> <span class="operator">=</span> QueryBuilders.rangeQuery(<span class="string">&quot;skuPrice&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (!StringUtils.isEmpty(startPrice)&amp;&amp;!StringUtils.isEmpty(endPrice))&#123;</span><br><span class="line">            rangeQuery.gte(startPrice).lte(endPrice);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(startPrice))&#123;</span><br><span class="line">            rangeQuery.lte(endPrice);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(endPrice))&#123;</span><br><span class="line">            rangeQuery.gte(startPrice);</span><br><span class="line">        &#125;</span><br><span class="line">        boolQuery.filter(rangeQuery);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 把之前的所有查询条件拿来进行封装</span></span><br><span class="line">    sourceBuilder.query(boolQuery);</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 排序、分页、高亮</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="comment">// 排序条件 销量、热度分、价格</span></span><br><span class="line">    <span class="keyword">if</span> (!StringUtils.isEmpty(param.getSort()))&#123;</span><br><span class="line">        <span class="comment">// 传递过来的参数sort=hotScore_asc/desc</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">sort</span> <span class="operator">=</span> param.getSort();</span><br><span class="line">        String[] s = sort.split(<span class="string">&quot;_&quot;</span>);</span><br><span class="line">        sourceBuilder.sort(s[<span class="number">0</span>],s[<span class="number">1</span>].equalsIgnoreCase(<span class="string">&quot;asc&quot;</span>)?SortOrder.ASC:SortOrder.DESC);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 分页</span></span><br><span class="line">    <span class="comment">// pageNum:1 from:0</span></span><br><span class="line">    sourceBuilder.from((param.getPageNum()-<span class="number">1</span>)*EsConstant.PAGE_SIZE);</span><br><span class="line">    sourceBuilder.size(EsConstant.PAGE_SIZE);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 聚合分析</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">TermsAggregationBuilder</span> <span class="variable">brandAgg</span> <span class="operator">=</span> AggregationBuilders.terms(<span class="string">&quot;brand_agg&quot;</span>);</span><br><span class="line">    brandAgg.field(<span class="string">&quot;brandId&quot;</span>).size(<span class="number">50</span>);</span><br><span class="line">    <span class="comment">// 品牌聚合的子聚合</span></span><br><span class="line">    brandAgg.subAggregation(AggregationBuilders.terms(<span class="string">&quot;brand_name_agg&quot;</span>).field(<span class="string">&quot;brandName&quot;</span>).size(<span class="number">10</span>));</span><br><span class="line">    brandAgg.subAggregation(AggregationBuilders.terms(<span class="string">&quot;brand_img_agg&quot;</span>).field(<span class="string">&quot;brandImg&quot;</span>).size(<span class="number">10</span>));</span><br><span class="line">    sourceBuilder.aggregation(brandAgg);</span><br><span class="line">    <span class="comment">// 分类聚合</span></span><br><span class="line">    TermsAggregationBuilder catalog_agg= AggregationBuilders.terms(<span class="string">&quot;catalog_agg&quot;</span>).field(<span class="string">&quot;catalogId&quot;</span>).size(<span class="number">10</span>);</span><br><span class="line">    catalog_agg.subAggregation(AggregationBuilders.terms(<span class="string">&quot;catalog_name_agg&quot;</span>).field(<span class="string">&quot;catalogName.keyword&quot;</span>).size(<span class="number">10</span>));</span><br><span class="line">    sourceBuilder.aggregation(catalog_agg);</span><br><span class="line">    <span class="comment">// 属性聚合</span></span><br><span class="line">    <span class="type">NestedAggregationBuilder</span> <span class="variable">attr_agg</span> <span class="operator">=</span> AggregationBuilders.nested(<span class="string">&quot;attr_agg&quot;</span>, <span class="string">&quot;attrs&quot;</span>);</span><br><span class="line">    TermsAggregationBuilder attr_id_agg= AggregationBuilders.terms(<span class="string">&quot;attr_id_agg&quot;</span>).field(<span class="string">&quot;attrs.attrId&quot;</span>).size(<span class="number">10</span>);</span><br><span class="line">    attr_id_agg.subAggregation(AggregationBuilders.terms(<span class="string">&quot;attr_name_agg&quot;</span>).field(<span class="string">&quot;attrs.attrName&quot;</span>).size(<span class="number">10</span>));</span><br><span class="line">    attr_id_agg.subAggregation(AggregationBuilders.terms(<span class="string">&quot;attr_value_agg&quot;</span>).field(<span class="string">&quot;attrs.attrValue&quot;</span>).size(<span class="number">10</span>));</span><br><span class="line">    attr_agg.subAggregation(attr_id_agg);</span><br><span class="line">    sourceBuilder.aggregation(attr_agg);</span><br><span class="line"></span><br><span class="line">    System.out.println(sourceBuilder.toString());</span><br><span class="line">    <span class="type">SearchRequest</span> <span class="variable">searchRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchRequest</span>(<span class="keyword">new</span> <span class="title class_">String</span>[]&#123;EsConstant.PRODUCT_INDEX&#125;, sourceBuilder);</span><br><span class="line">    <span class="keyword">return</span> searchRequest;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="SearchResult封装返回数据"><a href="#SearchResult封装返回数据" class="headerlink" title="SearchResult封装返回数据"></a>SearchResult封装返回数据</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> SearchResultVo <span class="title function_">buildSearchResult</span><span class="params">(SearchParamVo param, SearchResponse response)</span> &#123;</span><br><span class="line">    <span class="type">SearchResultVo</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchResultVo</span>();</span><br><span class="line">    <span class="comment">// 命中记录</span></span><br><span class="line">    <span class="type">SearchHits</span> <span class="variable">hits</span> <span class="operator">=</span> response.getHits();</span><br><span class="line">    ArrayList&lt;SkuEsModel&gt; skuEsModels = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">if</span> (!StringUtils.isEmpty(hits)) &#123;</span><br><span class="line">        <span class="keyword">for</span> (SearchHit hit : hits.getHits()) &#123;</span><br><span class="line">            <span class="comment">// 这个sourceAsString数据是JSON的字符串形式</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">sourceAsString</span> <span class="operator">=</span> hit.getSourceAsString();</span><br><span class="line">            <span class="comment">// 简单对象可以直接转</span></span><br><span class="line">            <span class="type">SkuEsModel</span> <span class="variable">skuEsModel</span> <span class="operator">=</span> JSON.parseObject(sourceAsString, SkuEsModel.class);</span><br><span class="line">            skuEsModels.add(skuEsModel);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 返回所有查询到的商品信息</span></span><br><span class="line">    result.setProducts(skuEsModels);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//2、当前商品涉及到的所有属性信息</span></span><br><span class="line">    List&lt;SearchResultVo.AttrVo&gt; attrVos = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="comment">//获取属性信息的聚合</span></span><br><span class="line">    <span class="type">ParsedNested</span> <span class="variable">attrsAgg</span> <span class="operator">=</span> response.getAggregations().get(<span class="string">&quot;attr_agg&quot;</span>);</span><br><span class="line">    <span class="type">ParsedLongTerms</span> <span class="variable">attrIdAgg</span> <span class="operator">=</span> attrsAgg.getAggregations().get(<span class="string">&quot;attr_id_agg&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (Terms.Bucket bucket : attrIdAgg.getBuckets()) &#123;</span><br><span class="line">        SearchResultVo.<span class="type">AttrVo</span> <span class="variable">attrVo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchResultVo</span>.AttrVo();</span><br><span class="line">        <span class="comment">//1、得到属性的id</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">attrId</span> <span class="operator">=</span> bucket.getKeyAsNumber().longValue();</span><br><span class="line">        attrVo.setAttrId(attrId);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//2、得到属性的名字</span></span><br><span class="line">        <span class="type">ParsedStringTerms</span> <span class="variable">attrNameAgg</span> <span class="operator">=</span> bucket.getAggregations().get(<span class="string">&quot;attr_name_agg&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">attrName</span> <span class="operator">=</span> attrNameAgg.getBuckets().get(<span class="number">0</span>).getKeyAsString();</span><br><span class="line">        attrVo.setAttrName(attrName);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//3、得到属性的所有值</span></span><br><span class="line">        <span class="type">ParsedStringTerms</span> <span class="variable">attrValueAgg</span> <span class="operator">=</span> bucket.getAggregations().get(<span class="string">&quot;attr_value_agg&quot;</span>);</span><br><span class="line">        List&lt;String&gt; attrValues = attrValueAgg.getBuckets().stream().map(item -&gt; item.getKeyAsString()).collect(Collectors.toList());</span><br><span class="line">        attrVo.setAttrValue(attrValues);</span><br><span class="line"></span><br><span class="line">        attrVos.add(attrVo);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    result.setAttrs(attrVos);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//3、当前商品涉及到的所有品牌信息</span></span><br><span class="line">    List&lt;SearchResultVo.BrandVo&gt; brandVos = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="comment">//获取到品牌的聚合</span></span><br><span class="line">    <span class="type">ParsedLongTerms</span> <span class="variable">brandAgg</span> <span class="operator">=</span> response.getAggregations().get(<span class="string">&quot;brand_agg&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (Terms.Bucket bucket : brandAgg.getBuckets()) &#123;</span><br><span class="line">        SearchResultVo.<span class="type">BrandVo</span> <span class="variable">brandVo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchResultVo</span>.BrandVo();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//1、得到品牌的id</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">brandId</span> <span class="operator">=</span> bucket.getKeyAsNumber().longValue();</span><br><span class="line">        brandVo.setBrandId(brandId);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//2、得到品牌的名字</span></span><br><span class="line">        <span class="type">ParsedStringTerms</span> <span class="variable">brandNameAgg</span> <span class="operator">=</span> bucket.getAggregations().get(<span class="string">&quot;brand_name_agg&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">brandName</span> <span class="operator">=</span> brandNameAgg.getBuckets().get(<span class="number">0</span>).getKeyAsString();</span><br><span class="line">        brandVo.setBrandName(brandName);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//3、得到品牌的图片</span></span><br><span class="line">        <span class="type">ParsedStringTerms</span> <span class="variable">brandImgAgg</span> <span class="operator">=</span> bucket.getAggregations().get(<span class="string">&quot;brand_img_agg&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">brandImg</span> <span class="operator">=</span> brandImgAgg.getBuckets().get(<span class="number">0</span>).getKeyAsString();</span><br><span class="line">        brandVo.setBrandImg(brandImg);</span><br><span class="line"></span><br><span class="line">        brandVos.add(brandVo);</span><br><span class="line">    &#125;</span><br><span class="line">    result.setBrands(brandVos);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当前商品的分类信息</span></span><br><span class="line">    List&lt;SearchResultVo.CatalogVo&gt; catalogVos = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">ParsedLongTerms</span> <span class="variable">catalogAgg</span> <span class="operator">=</span> response.getAggregations().get(<span class="string">&quot;catalog_agg&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (Terms.Bucket bucket : catalogAgg.getBuckets()) &#123;</span><br><span class="line">        SearchResultVo.<span class="type">CatalogVo</span> <span class="variable">catalogVo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SearchResultVo</span>.CatalogVo();</span><br><span class="line">        <span class="comment">//得到分类id</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">keyAsString</span> <span class="operator">=</span> bucket.getKeyAsString();</span><br><span class="line">        catalogVo.setCatalogId(Long.parseLong(keyAsString));</span><br><span class="line"></span><br><span class="line">        <span class="comment">//得到分类名</span></span><br><span class="line">        <span class="type">ParsedStringTerms</span> <span class="variable">catalogNameAgg</span> <span class="operator">=</span> bucket.getAggregations().get(<span class="string">&quot;catalog_name_agg&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">catalogName</span> <span class="operator">=</span> catalogNameAgg.getBuckets().get(<span class="number">0</span>).getKeyAsString();</span><br><span class="line">        catalogVo.setCatalogName(catalogName);</span><br><span class="line">        catalogVos.add(catalogVo);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    result.setCatalogs(catalogVos);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 分页信息-页码</span></span><br><span class="line">    result.setPageNum(param.getPageNum());</span><br><span class="line">    <span class="comment">// 总记录数</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">total</span> <span class="operator">=</span> hits.getTotalHits().value;</span><br><span class="line">    result.setTotal(total);</span><br><span class="line">    <span class="comment">// 总页数---需要计算</span></span><br><span class="line">    result.setTotalPages((<span class="type">int</span>) (total / EsConstant.PAGE_SIZE == <span class="number">0</span> ? total / EsConstant.PAGE_SIZE : total / EsConstant.PAGE_SIZE + <span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="商品详情"><a href="#商品详情" class="headerlink" title="商品详情"></a>商品详情</h3><blockquote>
<p>环境搭建</p>
</blockquote>
<p>192.168.253.128 item.gulimall.com </p>
<p>修改Nginx配置  *.gulimall.com</p>
<p>修改网关gateway </p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">- id<span class="punctuation">:</span> gulimall_host_route</span><br><span class="line">  uri<span class="punctuation">:</span> lb<span class="punctuation">:</span><span class="comment">//gulimall-product</span></span><br><span class="line">  predicates<span class="punctuation">:</span></span><br><span class="line">    - Host=gulimall.com<span class="punctuation">,</span>item.gulimall.com</span><br></pre></td></tr></table></figure>

<p>将商品详情的静态资源放到Nginx</p>
<p>访问商品详情：<a target="_blank" rel="noopener" href="http://item.gulimall.com/1.html">http://item.gulimall.com/1.html</a></p>
<blockquote>
<p>查询商品详情</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> SkuItemVo <span class="title function_">item</span><span class="params">(Long skuId)</span> &#123;</span><br><span class="line">    <span class="type">SkuItemVo</span> <span class="variable">skuItemVo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SkuItemVo</span>();</span><br><span class="line">    <span class="comment">// 1. 获取Sku的基本信息</span></span><br><span class="line">    <span class="type">SkuInfoEntity</span> <span class="variable">skuInfo</span> <span class="operator">=</span> getById(skuId);</span><br><span class="line">    <span class="type">Long</span> <span class="variable">spuId</span> <span class="operator">=</span> skuInfo.getSpuId();</span><br><span class="line">    skuItemVo.setSkuInfoEntity(skuInfo);</span><br><span class="line">    <span class="comment">// 2. 获取Sku的图片信息 sku_images</span></span><br><span class="line">    List&lt;SkuImagesEntity&gt; skuImages= skuImagesService.list(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;SkuImagesEntity&gt;().eq(<span class="string">&quot;sku_id&quot;</span>, skuId));</span><br><span class="line">    skuItemVo.setImages(skuImages);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3. 销售属性，获取SPU的销售属性组合</span></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 传入SpuId</span></span><br><span class="line"><span class="comment">     * 1. 分析当前spuId有多个sku,所有sku涉及到属性组合</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    List&lt;ItemSaleAttrsVo&gt; saleAttrs=skuSaleAttrValueService.getSaleAttrsBySpuId(spuId);</span><br><span class="line">    skuItemVo.setSaleAttrs(saleAttrs);</span><br><span class="line">    <span class="comment">// 4. 商品介绍，共享SPU的介绍</span></span><br><span class="line">    <span class="type">SpuInfoEntity</span> <span class="variable">spuInfo</span> <span class="operator">=</span> spuInfoService.getById(spuId);</span><br><span class="line">    skuItemVo.setDesp(spuInfo);</span><br><span class="line">    <span class="comment">// 5. spu规格参数信息</span></span><br><span class="line">    <span class="type">Long</span> <span class="variable">catalogId</span> <span class="operator">=</span> skuInfo.getCatalogId();</span><br><span class="line">    List&lt;SpuItemBaseAttrGroupVo&gt; attrGroupVos=attrGroupService.getAttrGroupWithAttrsBySpuId(catalogId,skuId);</span><br><span class="line">    <span class="keyword">return</span> skuItemVo;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//5. spu规格参数信息的查询语句</span></span><br></pre></td></tr></table></figure>

<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--只要有嵌套属性就要封装自定义结果--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">resultMap</span> <span class="attr">id</span>=<span class="string">&quot;spuAttrGroup&quot;</span> <span class="attr">type</span>=<span class="string">&quot;com.atguigu.gulimall.product.vo.SpuItemBaseAttrGroupVo&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">result</span> <span class="attr">property</span>=<span class="string">&quot;groupName&quot;</span> <span class="attr">column</span>=<span class="string">&quot;attr_group_name&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">collection</span> <span class="attr">property</span>=<span class="string">&quot;attrs&quot;</span> <span class="attr">ofType</span>=<span class="string">&quot;com.atguigu.gulimall.product.vo.SpuBaseAttrVo&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">property</span>=<span class="string">&quot;attrName&quot;</span> <span class="attr">column</span>=<span class="string">&quot;attr_name&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">result</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">result</span> <span class="attr">property</span>=<span class="string">&quot;attrValue&quot;</span> <span class="attr">column</span>=<span class="string">&quot;attr_value&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">result</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">collection</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">resultMap</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;getAttrGroupWithAttrsBySpuId&quot;</span> <span class="attr">resultMap</span>=<span class="string">&quot;spuAttrGroup&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    SELECT</span><br><span class="line">        product.spu_id,</span><br><span class="line">        pag.attr_group_id,</span><br><span class="line">        pag.attr_group_name,</span><br><span class="line">        product.attr_id,</span><br><span class="line">        product.attr_name,</span><br><span class="line">        product.attr_value</span><br><span class="line">    FROM</span><br><span class="line">        pms_product_attr_value product</span><br><span class="line">            LEFT JOIN pms_attr_attrgroup_relation paar ON product.attr_id = paar.attr_id</span><br><span class="line">            LEFT JOIN pms_attr_group pag ON paar.attr_group_id = pag.attr_group_id</span><br><span class="line">    WHERE</span><br><span class="line">        product.spu_id = #&#123;spuId&#125;</span><br><span class="line">      AND pag.catelog_id = #&#123;catalogId&#125;</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 3. 销售属性，获取SPU的销售属性组合</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 传入SpuId</span></span><br><span class="line"><span class="comment"> * 1. 分析当前spuId有多个sku,所有sku涉及到属性组合</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>

<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;getSaleAttrsBySpuId&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;com.atguigu.gulimall.product.vo.ItemSaleAttrsVo&quot;</span>&gt;</span></span><br><span class="line">    select</span><br><span class="line">        ssav.attr_id attrId,</span><br><span class="line">        ssav.attr_name attrName,</span><br><span class="line">        group_concat(distinct ssav.attr_value) attrValues</span><br><span class="line">    from pms_sku_info info</span><br><span class="line">    left join pms_sku_sale_attr_value ssav on ssav.sku_id=info.sku_id</span><br><span class="line">    where info.spu_id=#&#123;spuId&#125;</span><br><span class="line">    group by ssav.attr_id, ssav.attr_name</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h4 id="商品详情的异步编排"><a href="#商品详情的异步编排" class="headerlink" title="商品详情的异步编排"></a>商品详情的异步编排</h4><ol>
<li><p>线程池的配置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyThreadConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ThreadPoolExecutor <span class="title function_">threadPoolExecutor</span><span class="params">(ThreadPoolConfigProperties pool)</span>&#123;</span><br><span class="line">        <span class="comment">// 建立了一个ThreadPoolConfigProperties类，里面有核心线程等配置，</span></span><br><span class="line">        <span class="comment">// 因为已经注入到容器，可以直接调用</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(pool.getCoreSize(),</span><br><span class="line">                pool.getMaxSize(),</span><br><span class="line">                pool.getKeepAliveTime(),</span><br><span class="line">                TimeUnit.SECONDS,</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">LinkedBlockingDeque</span>&lt;&gt;(),</span><br><span class="line">                Executors.defaultThreadFactory(),</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>.AbortPolicy()</span><br><span class="line">                );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>在yaml中配置线程池大小、核心线程等</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ConfigurationProperties(prefix = &quot;gulimall.thread&quot;)</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadPoolConfigProperties</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer coreSize;</span><br><span class="line">    <span class="keyword">private</span> Integer maxSize;</span><br><span class="line">    <span class="keyword">private</span> Integer keepAliveTime;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>yaml中配置</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">gulimall:</span></span><br><span class="line">  <span class="attr">thread:</span></span><br><span class="line">    <span class="attr">core-size:</span> <span class="number">20</span></span><br><span class="line">    <span class="attr">max-size:</span> <span class="number">200</span></span><br><span class="line">    <span class="attr">keep-alive-time:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>优化原来的商品详情查询语句</strong></p>
</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> SkuItemVo <span class="title function_">item</span><span class="params">(Long skuId)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException &#123;</span><br><span class="line">    <span class="type">SkuItemVo</span> <span class="variable">skuItemVo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SkuItemVo</span>();</span><br><span class="line">    <span class="comment">// 1. 获取Sku的基本信息，因为3需要这个查询的返回值，所以用supplyAsync</span></span><br><span class="line">    CompletableFuture&lt;SkuInfoEntity&gt; infoFuture = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="type">SkuInfoEntity</span> <span class="variable">skuInfo</span> <span class="operator">=</span> getById(skuId);</span><br><span class="line">        skuItemVo.setSkuInfoEntity(skuInfo);</span><br><span class="line">        <span class="keyword">return</span> skuInfo;</span><br><span class="line">    &#125;, executor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 3 4 5都需要等待1执行完</span></span><br><span class="line"><span class="comment">     **/</span></span><br><span class="line">    <span class="comment">// 因为需要获取到1的结果，且3不用返回结果，采用thenAcceptAsync方法</span></span><br><span class="line">    CompletableFuture&lt;Void&gt; saleFuture = infoFuture.thenAcceptAsync((result) -&gt; &#123;</span><br><span class="line">        <span class="comment">// 3. 销售属性，获取SPU的销售属性组合</span></span><br><span class="line">        List&lt;ItemSaleAttrsVo&gt; saleAttrs = skuSaleAttrValueService.getSaleAttrsBySpuId(result.getSpuId());</span><br><span class="line">        skuItemVo.setSaleAttrs(saleAttrs);</span><br><span class="line">    &#125;, executor);</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Void&gt; productDescFuture = infoFuture.thenAcceptAsync((result) -&gt; &#123;</span><br><span class="line">        <span class="comment">// 4. 商品介绍，共享SPU的介绍</span></span><br><span class="line">        <span class="type">SpuInfoEntity</span> <span class="variable">spuInfo</span> <span class="operator">=</span> spuInfoService.getById(result.getSpuId());</span><br><span class="line">        skuItemVo.setDesp(spuInfo);</span><br><span class="line">    &#125;, executor);</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Void&gt; spuAttrFuture = infoFuture.thenAcceptAsync((result) -&gt; &#123;</span><br><span class="line">        <span class="comment">// 5. spu规格参数信息</span></span><br><span class="line">        List&lt;SpuItemBaseAttrGroupVo&gt; attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(result.getCatalogId(), skuId);</span><br><span class="line">        skuItemVo.setGroupAttrs(attrGroupVos);</span><br><span class="line">    &#125;, executor);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 2和其它没有关系，可以再开一个异步线程</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    CompletableFuture&lt;Void&gt; skuFuture  = CompletableFuture.runAsync(() -&gt; &#123;</span><br><span class="line">        <span class="comment">// 2. 获取Sku的图片信息 sku_images</span></span><br><span class="line">        List&lt;SkuImagesEntity&gt; skuImages = skuImagesService.list(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;SkuImagesEntity&gt;().eq(<span class="string">&quot;sku_id&quot;</span>, skuId));</span><br><span class="line">        skuItemVo.setImages(skuImages);</span><br><span class="line">    &#125;, executor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 必须等待全部执行完，才能输出结果infoFuture可以省略，因为saleFuture等都是基于这个的</span></span><br><span class="line">    CompletableFuture&lt;Void&gt; allOf = CompletableFuture.allOf(saleFuture, productDescFuture, spuAttrFuture, skuFuture);</span><br><span class="line">    allOf.get(); <span class="comment">// get()方法，阻塞式等待，等待所有都执行完</span></span><br><span class="line">    <span class="keyword">return</span> skuItemVo;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="认证服务"><a href="#认证服务" class="headerlink" title="认证服务"></a>认证服务</h3><p>认证服务地址：192.168.253.128 auth.gulimall.com </p>
<p>静态资源上传到Nginx</p>
<p>访问登录页<a target="_blank" rel="noopener" href="http://auth.gulimall.com/">http://auth.gulimall.com/</a></p>
<h4 id="UserRegisVo接收注册信息的对象"><a href="#UserRegisVo接收注册信息的对象" class="headerlink" title="UserRegisVo接收注册信息的对象"></a>UserRegisVo接收注册信息的对象</h4><blockquote>
<p>接收注册信息的对象</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserRegisVo</span> &#123;</span><br><span class="line">    <span class="meta">@NotEmpty(message = &quot;用户名必须提交&quot;)</span></span><br><span class="line">    <span class="meta">@Length(min = 6, max = 18, message = &quot;用户名必须6到8为字符&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String userName;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@NotEmpty(message = &quot;密码必须填写&quot;)</span></span><br><span class="line">    <span class="meta">@Length(min = 6, max = 18, message = &quot;用户名必须6到8为字符&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@NotEmpty(message = &quot;手机号必须填写&quot;)</span></span><br><span class="line">    <span class="meta">@Pattern(regexp = &quot;^[1]([3-9])[0-9]&#123;9&#125;$&quot;, message = &quot;手机格式不正确&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String phone;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@NotEmpty(message = &quot;验证码&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String code;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>@NotEmpty被注解的对象不能为null或在empty</p>
<p>@NotBlank被注解的对象不能为null且必须包含一个非空格字符</p>
<p>@Min @Max @Pattern</p>
<p><strong>@Validated注解，在接收需要JSR303的对象</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PostMapping(&quot;/reg.html&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">reg</span><span class="params">(<span class="meta">@Validated</span> UserRegisVo userRegisVo, BindingResult result, Model model)</span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (result.hasErrors())&#123;</span><br><span class="line">        Map&lt;String, String&gt; errors = result.getFieldErrors().stream().collect(Collectors.toMap(</span><br><span class="line">                fieldError -&gt; fieldError.getField(),</span><br><span class="line">                fieldError -&gt; fieldError.getDefaultMessage()</span><br><span class="line">        ));</span><br><span class="line">        model.addAttribute(<span class="string">&quot;errors&quot;</span>,errors);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;reg&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="密码加密MD5或MD5盐值加密"><a href="#密码加密MD5或MD5盐值加密" class="headerlink" title="密码加密MD5或MD5盐值加密"></a>密码加密MD5或MD5盐值加密</h4><ul>
<li>MD5：<strong>信息摘要算法</strong>，<strong>任意长度的数据，算出的MD5的长度都是固定的</strong><ol>
<li>抗修改性：修改一个字节，MD5值都不一样，<strong>因为抗修改性：例如123456的MD5值是固定的，可以暴力破解</strong></li>
<li>强抗碰撞：很难找到两个不同的MD5值</li>
</ol>
</li>
<li>加盐：通过生成随机数字与MD5生成字符串进行组合。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> DigestUtils.md5Hex(<span class="string">&quot;123456&quot;</span>);</span><br><span class="line"><span class="comment">//利用MD5的抗修改性，可以进行暴力破解</span></span><br><span class="line">System.out.println(s);</span><br><span class="line"><span class="comment">//盐值加密（随机值）:加盐（）</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> Md5Crypt.md5Crypt(<span class="string">&quot;123456&quot;</span>.getBytes());</span><br><span class="line">System.out.println(s1);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 验证时，在将原密码和盐值进行加密比较</span></span><br></pre></td></tr></table></figure>

<h4 id="BCryptPasswordEncoder密码加密器"><a href="#BCryptPasswordEncoder密码加密器" class="headerlink" title="BCryptPasswordEncoder密码加密器"></a>BCryptPasswordEncoder密码加密器</h4><p>Spring也提供了一种密码加密器，同一个密码每次加密的结果不同，但是匹配比较时还可以正确匹配<br><strong>盐值融合到了密文中，不需要再加额外的盐值字段</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">BCryptPasswordEncoder</span> <span class="variable">bCryptPasswordEncoder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BCryptPasswordEncoder</span>();</span><br><span class="line"><span class="type">String</span> <span class="variable">encode</span> <span class="operator">=</span> bCryptPasswordEncoder.encode(<span class="string">&quot;123456&quot;</span>);</span><br><span class="line"><span class="comment">// $2a$10$4S4XHX2Dc678mIIAppsH.e5BWznRb.DslTyt1oxP9O4GGFOORyFca</span></span><br><span class="line">System.out.println(encode);</span><br><span class="line"><span class="type">boolean</span> <span class="variable">matches</span> <span class="operator">=</span> bCryptPasswordEncoder.matches(<span class="string">&quot;123456&quot;</span>, <span class="string">&quot;$2a$10$4S4XHX2Dc678mIIAppsH.e5BWznRb.DslTyt1oxP9O4GGFOORyFca&quot;</span>);</span><br><span class="line">System.out.println(matches);</span><br></pre></td></tr></table></figure>

<h3 id="社交登录"><a href="#社交登录" class="headerlink" title="社交登录"></a>社交登录</h3><p>利用QQ或微信等登录</p>
<h4 id="OAuth2-0"><a href="#OAuth2-0" class="headerlink" title="OAuth2.0"></a>OAuth2.0</h4><p>对于用户相关的API，为了保护用户数据安全和隐私，第三方网站访问都需要显式的向用户征求授权。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108155926418.png" alt="image-20231108155926418"></p>
<p>用户授权后，第三方会返回一个Code，客户端再根据第三方提供的接口通过Code查询访问令牌，最后通过令牌访问到用户信息。<strong>使用Code换取AccessToken，Code只能用一次</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(value = &quot;/oauth2.0/weibo/success&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">weibo</span><span class="params">(<span class="meta">@RequestParam(&quot;code&quot;)</span> String code, HttpSession session)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">    Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    map.put(<span class="string">&quot;client_id&quot;</span>,<span class="string">&quot;2077705774&quot;</span>);</span><br><span class="line">    map.put(<span class="string">&quot;client_secret&quot;</span>,<span class="string">&quot;40af02bd1c7e435ba6a6e9cd3bf799fd&quot;</span>);</span><br><span class="line">    map.put(<span class="string">&quot;grant_type&quot;</span>,<span class="string">&quot;authorization_code&quot;</span>);</span><br><span class="line">    map.put(<span class="string">&quot;redirect_uri&quot;</span>,<span class="string">&quot;http://auth.gulimall.com/oauth2.0/weibo/success&quot;</span>);</span><br><span class="line">    map.put(<span class="string">&quot;code&quot;</span>,code);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//1、根据用户授权返回的code换取access_token</span></span><br><span class="line">    <span class="type">HttpResponse</span> <span class="variable">response</span> <span class="operator">=</span> HttpUtils.doPost(<span class="string">&quot;https://api.weibo.com&quot;</span>, <span class="string">&quot;/oauth2/access_token&quot;</span>, <span class="string">&quot;post&quot;</span>, <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;(), map, <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;());</span><br><span class="line"></span><br><span class="line">    <span class="comment">//2、处理</span></span><br><span class="line">    <span class="keyword">if</span> (response.getStatusLine().getStatusCode() == <span class="number">200</span>) &#123;</span><br><span class="line">        <span class="comment">//获取到了access_token,转为通用社交登录对象</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">json</span> <span class="operator">=</span> EntityUtils.toString(response.getEntity());</span><br><span class="line">        <span class="comment">// SocialUser是微博响应的对应的封装对象</span></span><br><span class="line">        <span class="type">SocialUser</span> <span class="variable">socialUser</span> <span class="operator">=</span> JSON.parseObject(json, SocialUser.class);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//知道了哪个社交用户</span></span><br><span class="line">        <span class="comment">//1）、当前用户如果是第一次进网站，自动注册进来（为当前社交用户生成一个会员信息，以后这个社交账号就对应指定的会员）</span></span><br><span class="line">        <span class="comment">//登录或者注册这个社交用户</span></span><br><span class="line">        System.out.println(socialUser.getAccess_token());</span><br><span class="line">        <span class="comment">//调用远程服务</span></span><br><span class="line">        <span class="type">R</span> <span class="variable">oauthLogin</span> <span class="operator">=</span> memberFeignService.oauthLogin(socialUser);</span><br><span class="line">        <span class="keyword">if</span> (oauthLogin.getCode() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">MemberResponseVo</span> <span class="variable">data</span> <span class="operator">=</span> oauthLogin.getData(<span class="string">&quot;data&quot;</span>, <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;MemberResponseVo&gt;() &#123;&#125;);</span><br><span class="line">            log.info(<span class="string">&quot;登录成功：用户信息：&#123;&#125;&quot;</span>,data.toString());</span><br><span class="line"></span><br><span class="line">            <span class="comment">//1、第一次使用session，命令浏览器保存卡号，JSESSIONID这个cookie</span></span><br><span class="line">            <span class="comment">//以后浏览器访问哪个网站就会带上这个网站的cookie</span></span><br><span class="line">            <span class="comment">//TODO 1、默认发的令牌。当前域（解决子域session共享问题）</span></span><br><span class="line">            <span class="comment">//TODO 2、使用JSON的序列化方式来序列化对象到Redis中</span></span><br><span class="line">            session.setAttribute(LOGIN_USER,data);</span><br><span class="line"></span><br><span class="line">            <span class="comment">//2、登录成功跳回首页</span></span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;redirect:http://gulimall.com&quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;redirect:http://auth.gulimall.com/login.html&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;redirect:http://auth.gulimall.com/login.html&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="不同域名session信息不共享"><a href="#不同域名session信息不共享" class="headerlink" title="不同域名session信息不共享"></a>不同域名session信息不共享</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108174209780.png" alt="image-20231108174209780"></p>
<p>通过Cookie数据去当前域名的session中查找对应数据，如果有，则登录。</p>
<p><strong>session其实是服务器中一块内存空间，底层是Map</strong>。</p>
<p><strong>不同域名之间的session的不能共享信息</strong>。如 auth.gulimall.com和gulimall.com</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108174709342.png" alt="image-20231108174709342"></p>
<ul>
<li>同一个服务复制多份session，session不同步</li>
<li>不同服务session不共享</li>
</ul>
<blockquote>
<p>同一域名下session共享问题解决</p>
</blockquote>
<ul>
<li><p><strong>session复制</strong></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175143004.png" alt="image-20231108175143004"></p>
</li>
<li><p><strong>客户端存储</strong></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175348858.png" alt="image-20231108175348858"></p>
</li>
<li><p>hash一致性</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175516621.png" alt="image-20231108175516621"></p>
</li>
<li><p>统一存储</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108175743990.png" alt="image-20231108175743990"></p>
</li>
</ul>
<p>采用Redis统一存储session。</p>
<blockquote>
<p>不同服务，子域session共享，通过SpringSession解决</p>
</blockquote>
<p>可以在保存时，<strong>设置session的作用域，放大作用域</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">session.setAttribute(LOGIN_USER,data);</span><br><span class="line"><span class="comment">// 作用域不指定，默认就是当前服务器域名</span></span><br><span class="line">servletResponse.addCookie(<span class="keyword">new</span> <span class="title class_">Cookie</span>(<span class="string">&quot;JSSESSION&quot;</span>,<span class="string">&quot;fafewrfsa&quot;</span>).setDomain(<span class="string">&quot;gulimall.com&quot;</span>)));</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108180935288.png" alt="image-20231108180935288"></p>
<p>不同服务给浏览器发的卡，即Cookie信息，对应的session信息全部统一存储到Redis中</p>
<h4 id="SpringSession解决，不同域名的Session共享"><a href="#SpringSession解决，不同域名的Session共享" class="headerlink" title="SpringSession解决，不同域名的Session共享"></a>SpringSession解决，不同域名的Session共享</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">			&lt;groupId&gt;org.springframework.session&lt;/groupId&gt;</span><br><span class="line">			&lt;artifactId&gt;spring-session-data-redis&lt;/artifactId&gt;</span><br><span class="line">			&lt;version&gt;2.1.8.RELEASE&lt;/version&gt;</span><br><span class="line">		&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">spring</span><br><span class="line">    #配置Redis相关信息</span><br><span class="line">    redis:</span><br><span class="line">    	host: <span class="number">192.168</span><span class="number">.253</span><span class="number">.128</span></span><br><span class="line">    	port: <span class="number">6379</span></span><br><span class="line">    session:</span><br><span class="line">  		store-type: redis</span><br><span class="line">  		timeout: 30m</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@EnableRedisHttpSession</span>  整合Redis作为Session存储</span><br></pre></td></tr></table></figure>

<p><strong>程序往Session中存储数据</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 默认Session作用域为当前域</span></span><br><span class="line">session.setAttribute(<span class="string">&quot;userName&quot;</span>,<span class="string">&quot;黄宏涛&quot;</span>);</span><br></pre></td></tr></table></figure>

<p><strong>浏览器中Cookie数据</strong>，即服务器向浏览器发的一个ID，用户再次登录时，拿着这个Cookie，去服务器中的Redis获取相关的用户信息。<strong>但是这个作用域为子域名，若在gulimall.com中无法获取到这个Session。</strong></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108183454233.png" alt="image-20231108183454233"></p>
<p><span style="color:red;font-weight:bold">往Redis中存储对象时，需要实现序列化接口（JDK序列化）或者使用JSON序列化， implements Serializable </span></p>
<blockquote>
<p>SpringSession官网</p>
</blockquote>
<p><a target="_blank" rel="noopener" href="https://docs.spring.io/spring-session/docs/2.2.1.RELEASE/reference/html5/#samples">https://docs.spring.io/spring-session/docs/2.2.1.RELEASE/reference/html5/#samples</a></p>
<p>JSON序列化的方式</p>
<blockquote>
<p><strong>指定作用域</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@EnableRedisHttpSession</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GulimallSessionConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CookieSerializer <span class="title function_">cookieSerializer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">DefaultCookieSerializer</span> <span class="variable">serializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultCookieSerializer</span>();</span><br><span class="line">        serializer.setCookieName(<span class="string">&quot;GULISESSION&quot;</span>);</span><br><span class="line">        serializer.setDomainName(<span class="string">&quot;gulimall.com&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> serializer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 采用JSON序列化</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisSerializer&lt;Object&gt; <span class="title function_">springSessionDefaultRedisSerializer</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span>  <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="SpringSession原理"><a href="#SpringSession原理" class="headerlink" title="SpringSession原理"></a>SpringSession原理</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108193411820.png" alt="image-20231108193411820"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231108193446525.png" alt="image-20231108193446525"></p>
<h3 id="单点登录"><a href="#单点登录" class="headerlink" title="单点登录"></a>单点登录</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109093149937.png" alt="image-20231109093149937"></p>
<p><strong>多系统的登录，SpringSession无法解决</strong>。<strong>SpringSession只能解决父域名相同的</strong>，如gulimall.com和auth.gulimall.com</p>
<p>开源框架xxl</p>
<h3 id="购物车"><a href="#购物车" class="headerlink" title="购物车"></a>购物车</h3><h4 id="数据模型分析"><a href="#数据模型分析" class="headerlink" title="数据模型分析"></a>数据模型分析</h4><ul>
<li><p>用户购物车：登录状态下的购物车，也会将离线购物车合并</p>
<p>读写都高，可以用MongoDB或Redis（<strong>采用</strong>）</p>
</li>
<li><p>离线购物车（未登录）：用户未登录，也可以添加到购物车，结算的时候必须登录，即使浏览器关闭，下次打开依然可以看到离线购物车内容。</p>
<p>临时购物车，可以放到localStore、Redis（<strong>采用</strong>）、Cookie、WebSQL。<strong>放在本地我们无法分析用户需求</strong>。</p>
</li>
</ul>
<blockquote>
<p>购物项</p>
</blockquote>
<ol>
<li>商品ID</li>
<li>商品选中状态</li>
<li>标题</li>
<li>价格</li>
<li>数量</li>
<li>sku对象</li>
</ol>
<blockquote>
<p>购物车</p>
</blockquote>
<p>多个对象的数组</p>
<blockquote>
<p><strong>购物车的储存到Redis中的数据结构：</strong></p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109101247412.png" alt="image-20231109101247412"></p>
<p>如果存为List数组，遍历很麻烦，降低性能，可以存为Map</p>
<ul>
<li><p>不同用户有不同的购物车，购物车应该是按照用户key来存储，Value存储购物车信息</p>
</li>
<li><p>但是我们对购物车商品进行修改，需要根据商品ID判断，为了方便，也可以采用Map处理</p>
<p>key是商品ID，Value是商品内容</p>
</li>
</ul>
<p>购物车结构为<strong>Map&lt;String 用户id,Map&lt;商品ID，商品内容&gt;&gt;</strong></p>
<blockquote>
<p>购物车Vo</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Setter</span></span><br><span class="line"><span class="comment">//整个购物车</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Cart</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;CartItem&gt; items;</span><br><span class="line">    <span class="keyword">private</span> Integer countNum; <span class="comment">//商品数量</span></span><br><span class="line">    <span class="keyword">private</span> Integer countType;<span class="comment">// 商品类型数量</span></span><br><span class="line">    <span class="keyword">private</span> BigDecimal totalAmount; <span class="comment">//总价</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List&lt;CartItem&gt; <span class="title function_">getItems</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> items;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计算所有不同商品数量</span></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getCountNum</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> count=<span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (items!=<span class="literal">null</span>&amp;&amp;items.size()&gt;<span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">for</span> (CartItem item : items) &#123;</span><br><span class="line">                count+=item.getCount();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Integer <span class="title function_">getCountType</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> count=<span class="number">0</span>;</span><br><span class="line">        <span class="keyword">if</span> (items!=<span class="literal">null</span>&amp;&amp;items.size()&gt;<span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">for</span> (CartItem item : items) &#123;</span><br><span class="line">                count+=<span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计算所有商品的总价</span></span><br><span class="line">    <span class="keyword">public</span> BigDecimal <span class="title function_">getTotalAmount</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">BigDecimal</span> <span class="variable">amountPrice</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (items!=<span class="literal">null</span>&amp;&amp;items.size()&gt;<span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">for</span> (CartItem item : items) &#123;</span><br><span class="line">                amountPrice=amountPrice.add(item.getPrice().multiply(</span><br><span class="line">                        <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;&quot;</span>+item.getCount())));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> amountPrice;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>购物项</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CartItem</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Long skuId;</span><br><span class="line">    <span class="keyword">private</span> String title;</span><br><span class="line">    <span class="keyword">private</span> Boolean check=<span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">private</span> String image;</span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; skuAttr;</span><br><span class="line">    <span class="comment">// 涉及到计算的需要BigDecimal</span></span><br><span class="line">    <span class="keyword">private</span> BigDecimal price;</span><br><span class="line">    <span class="keyword">private</span> Integer count;</span><br><span class="line">    <span class="keyword">private</span> BigDecimal totalPrice;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="ThreadLocal同一个线程共享数据"><a href="#ThreadLocal同一个线程共享数据" class="headerlink" title="ThreadLocal同一个线程共享数据"></a>ThreadLocal同一个线程共享数据</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109125738146.png" alt="image-20231109125738146"></p>
<p>一个请求进来，后续的处理就在一条线程上。<strong>Map&lt;Thread,Object&gt; threadLocal</strong>本质</p>
<blockquote>
<p>过滤器在方法执行之前判断用户是否登录</p>
</blockquote>
<ol>
<li><p>先注册拦截器，指定拦截路径</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注册拦截器,拦截路径</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">       registry.addInterceptor(<span class="keyword">new</span> <span class="title class_">CartInterceptor</span>()).</span><br><span class="line">               addPathPatterns(<span class="string">&quot;/**&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>添加拦截器，判断用户登录状态，未登录就用临时用户，如果是第一次登录先分配一个user-key，再在方法处理结束后，添加user-key到Cookie中。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 执行目标方法之前，判断用户的登录状态，并封装传递为controller目标请求</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CartInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 同一个线程共享数据</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ThreadLocal&lt;UserInfoTo&gt; threadLocal= <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">HttpSession</span> <span class="variable">session</span> <span class="operator">=</span> request.getSession();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">user</span> <span class="operator">=</span> session.getAttribute(<span class="string">&quot;user&quot;</span>);</span><br><span class="line">        <span class="type">UserInfoTo</span> <span class="variable">userInfoTo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserInfoTo</span>();</span><br><span class="line">        <span class="comment">// 判断用户是否登录</span></span><br><span class="line">        <span class="keyword">if</span> (user!=<span class="literal">null</span>)&#123;</span><br><span class="line">            <span class="comment">//用户已经登录</span></span><br><span class="line">            userInfoTo.setUserId(<span class="number">111L</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            Cookie[] cookies = request.getCookies();</span><br><span class="line">            <span class="keyword">if</span> (cookies != <span class="literal">null</span>)&#123;</span><br><span class="line">                <span class="keyword">for</span> (Cookie cookie : cookies) &#123;</span><br><span class="line">                    <span class="comment">//没登录，临时用户</span></span><br><span class="line">                    <span class="keyword">if</span> (cookie.getName().equals(<span class="string">&quot;user-key&quot;</span>))&#123;</span><br><span class="line">                        userInfoTo.setUserKey(cookie.getValue());</span><br><span class="line">                        userInfoTo.setTempUser(<span class="literal">true</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果临时用户为空，创建一个新的临时用户</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(userInfoTo.getUserKey()))&#123;</span><br><span class="line">            String uuid= UUID.randomUUID().toString();</span><br><span class="line">            userInfoTo.setUserKey(uuid);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 目标方法执行之前,使得同一个线程的信息共享</span></span><br><span class="line">        threadLocal.set(userInfoTo);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 业务执行之后，如果是临时用户，添加一个Cookie</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 如果请求中，是临时用户，添加一个Cookie</span></span><br><span class="line">        <span class="keyword">if</span> (!threadLocal.get().isTempUser())&#123;</span><br><span class="line">            <span class="type">Cookie</span> <span class="variable">cookie</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Cookie</span>(<span class="string">&quot;user-key&quot;</span>,threadLocal.get().getUserKey() );</span><br><span class="line">            cookie.setDomain(<span class="string">&quot;localhost&quot;</span>);</span><br><span class="line">            cookie.setMaxAge(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>*<span class="number">30</span>); <span class="comment">//过期时间为一个月；</span></span><br><span class="line">            response.addCookie(cookie);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
</li>
<li><p>使用ThreadLocal共享同一个线程上数据</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 同一个线程共享数据</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ThreadLocal&lt;UserInfoTo&gt; threadLocal= <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br></pre></td></tr></table></figure>

<p><strong>只要在一条执行链上，都可以共享， 拦截器—》controller—-》Service—-》Dao</strong></p>
</li>
</ol>
<h4 id="添加到购物车"><a href="#添加到购物车" class="headerlink" title="添加到购物车"></a>添加到购物车</h4><p>请求：**&#x2F;addCart**</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加到购物车页面</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/addCart&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">addCart</span><span class="params">(Model model)</span>&#123;</span><br><span class="line">        <span class="comment">// 此处直接指定skuId，和购买数量</span></span><br><span class="line">        CartItem cartItem=cartService.addCart(<span class="number">1L</span>,<span class="number">2</span>);</span><br><span class="line">        model.addAttribute(<span class="string">&quot;item&quot;</span>,cartItem);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;success&quot;</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>先判断用户的登录状态，指定一个cartKey，redisTemplate.boundHashOps(cartKey)绑定当前用户的购物车信息，</p>
<p>然后添加商品项到购物车。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加购物车</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> CartItem <span class="title function_">addCart</span><span class="params">(Long skuId, Integer num)</span> &#123;</span><br><span class="line">        <span class="comment">// 获取到购物车</span></span><br><span class="line">        BoundHashOperations&lt;String, Object, Object&gt; cartOps = getCartOps();</span><br><span class="line"></span><br><span class="line">        <span class="type">CartItem</span> <span class="variable">cartItem</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CartItem</span>();</span><br><span class="line">        <span class="keyword">if</span> (cartOps.hasKey(skuId.toString()))&#123;</span><br><span class="line">            <span class="comment">// 如果Redis购物车中有这个商品，更新数量</span></span><br><span class="line">            <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> cartOps.get(skuId.toString());</span><br><span class="line">            cartItem = JSON.parseObject((String) o, CartItem.class);</span><br><span class="line">            cartItem.setCount(cartItem.getCount()+num);</span><br><span class="line">            <span class="comment">// 更新数量</span></span><br><span class="line">            cartOps.put(skuId.toString(), JSON.toJSONString(cartItem));</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 2. 新商品添加到购物车,不查了，直接添加</span></span><br><span class="line">            cartItem.setImage(<span class="string">&quot;https://gullmall-hello1.oss-cn-beijing.aliyuncs.com/2023-10-21//3900447c-2fe0-4250-84cc-5e4cc9a88d28_2.jpg&quot;</span>);</span><br><span class="line">            cartItem.setTitle(<span class="string">&quot;HUAWEI Mate 60 RS 非凡大师 黑色 8+128&quot;</span>);</span><br><span class="line">            cartItem.setPrice(BigDecimal.valueOf(<span class="number">12999</span>));</span><br><span class="line">            cartItem.setSkuId(<span class="number">1L</span>);</span><br><span class="line">            cartItem.setCount(num);</span><br><span class="line">            <span class="comment">// 3. 远程查询sku的组合信息，此处也不查了</span></span><br><span class="line">            <span class="comment">// 把购物项放入到当前用户购物车</span></span><br><span class="line">            cartOps.put(skuId.toString(), JSON.toJSONString(cartItem));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> cartItem;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> BoundHashOperations&lt;String, Object, Object&gt; <span class="title function_">getCartOps</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 获取用户信息</span></span><br><span class="line">    <span class="type">UserInfoTo</span> <span class="variable">userInfoTo</span> <span class="operator">=</span> CartInterceptor.threadLocal.get();</span><br><span class="line">    <span class="comment">// 判断是在临时购物车还是登录购物车执行</span></span><br><span class="line">    String cartKey=<span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">if</span> (userInfoTo.getUserId()!=<span class="literal">null</span>)&#123;</span><br><span class="line">        cartKey=<span class="string">&quot;gulimall:cart:&quot;</span>+userInfoTo.getUserId();</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 临时购物车</span></span><br><span class="line">        cartKey=<span class="string">&quot;gulimall:cart:&quot;</span>+userInfoTo.getUserKey();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取当前用户购物车信息</span></span><br><span class="line">    <span class="keyword">return</span> redisTemplate.boundHashOps(cartKey);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<blockquote>
<p>Redis中商品的存储结构</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109145019209.png" alt="image-20231109145019209"></p>
<h4 id="避免页面刷新，商品数量一直增加RedirectAttributes"><a href="#避免页面刷新，商品数量一直增加RedirectAttributes" class="headerlink" title="避免页面刷新，商品数量一直增加RedirectAttributes"></a>避免页面刷新，商品数量一直增加RedirectAttributes</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231109153715881.png" alt="image-20231109153715881"></p>
<p><strong>解决方案：****重定向到另一个页面，每次刷新都从数据库里面查询</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// 添加到购物车页面</span></span><br><span class="line"><span class="meta">@GetMapping(&quot;/addCart&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">addCart</span><span class="params">(RedirectAttributes ra)</span>&#123;</span><br><span class="line">        <span class="comment">// 此处直接指定skuId，和购买数量</span></span><br><span class="line">        CartItem cartItem=cartService.addCart(<span class="number">1L</span>,<span class="number">2</span>); </span><br><span class="line">    <span class="comment">// RedirectAttributes此处相当于转发时在URL结尾拼接上&amp;skuId=....</span></span><br><span class="line">    <span class="comment">// 即 http://localhost:20000/addtoCartSuccess.html?skuId=1</span></span><br><span class="line">    ra.addAttribute(<span class="string">&quot;skuId&quot;</span>,<span class="number">1L</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;redirect:addtoCartSuccess.html&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping(&quot;/addtoCartSuccess.html&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">addToCartSuccessPage</span><span class="params">(<span class="meta">@RequestParam(&quot;skuId&quot;)</span> Long skuId, Model model)</span>&#123;</span><br><span class="line">    <span class="comment">// 重定向到成功页面，再次查询购物车数据即可</span></span><br><span class="line">    CartItem cartItem=cartService.getCartItem(skuId);</span><br><span class="line">    model.addAttribute(<span class="string">&quot;item&quot;</span>,cartItem);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;success&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="获取-合并购物车"><a href="#获取-合并购物车" class="headerlink" title="获取-合并购物车"></a>获取-合并购物车</h4><p>目前有两种购物车：已登录用户购物车和临时购物车</p>
<blockquote>
<p>假设用户已经登录</p>
</blockquote>
<h3 id="订单服务"><a href="#订单服务" class="headerlink" title="订单服务"></a>订单服务</h3><h4 id="订单基本概念"><a href="#订单基本概念" class="headerlink" title="订单基本概念"></a>订单基本概念</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231111185550463.png" alt="image-20231111185550463"></p>
<p>电商系统主要涉及到三部分：信息流、资金流、物流</p>
<p><strong>订单系统是电商系统的枢纽</strong></p>
<blockquote>
<p>订单状态</p>
</blockquote>
<ol>
<li><p>待付款：用户提交订单后</p>
</li>
<li><p>已付款&#x2F;待发货：用户完成订单支付，订单系统记录支付时间</p>
</li>
<li><p>待收货</p>
</li>
<li><p>已完成：用户确认收货</p>
</li>
<li><p>已取消：付款之前取消订单</p>
</li>
<li><p>售后中：付款后申请退款</p>
</li>
</ol>
<blockquote>
<p>订单流程</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231111192114175.png" alt="image-20231111192114175"></p>
<blockquote>
<p>幂等性处理</p>
</blockquote>
<p>重复提交</p>
<h4 id="Fegin远程调用丢失请求头"><a href="#Fegin远程调用丢失请求头" class="headerlink" title="Fegin远程调用丢失请求头"></a>Fegin远程调用丢失请求头</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231111214014129.png" alt="image-20231111214014129"></p>
<p>Fegin在远程调用之前会通过一个拦截器</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GuliFeignConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean(&quot;requestInterceptor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> RequestInterceptor <span class="title function_">requestInterceptor</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">RequestInterceptor</span> <span class="variable">requestInterceptor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestInterceptor</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">apply</span><span class="params">(RequestTemplate template)</span> &#123;</span><br><span class="line">                <span class="comment">//1、使用RequestContextHolder拿到刚进来的请求数据</span></span><br><span class="line">                <span class="type">ServletRequestAttributes</span> <span class="variable">requestAttributes</span> <span class="operator">=</span> (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (requestAttributes != <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="comment">//老请求</span></span><br><span class="line">                    <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> requestAttributes.getRequest();</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">if</span> (request != <span class="literal">null</span>) &#123;</span><br><span class="line">                        <span class="comment">//2、同步请求头的数据（主要是cookie）</span></span><br><span class="line">                        <span class="comment">//把老请求的cookie值放到新请求上来，进行一个同步</span></span><br><span class="line">                        <span class="type">String</span> <span class="variable">cookie</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Cookie&quot;</span>);</span><br><span class="line">                        template.header(<span class="string">&quot;Cookie&quot;</span>, cookie);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> requestInterceptor;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="Fegin异步情况下丢失上下文"><a href="#Fegin异步情况下丢失上下文" class="headerlink" title="Fegin异步情况下丢失上下文"></a>Fegin异步情况下丢失上下文</h4><p>因为开了异步编排，不是单线程，Feign请求发送时丢失上下文</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FeignConfig</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Feign在远程调用之前要构造请求，调用很多拦截器 如 RequestInterceptor</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean(&quot;requestInterceptor&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> RequestInterceptor <span class="title function_">requestInterceptor</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RequestInterceptor</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">apply</span><span class="params">(RequestTemplate requestTemplate)</span> &#123;</span><br><span class="line">                <span class="type">ServletRequestAttributes</span> <span class="variable">attributes</span> <span class="operator">=</span>(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();</span><br><span class="line">                <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> attributes.getRequest();<span class="comment">//老请求</span></span><br><span class="line">                <span class="keyword">if</span> (request!=<span class="literal">null</span>)&#123;</span><br><span class="line">                    System.out.println(attributes);</span><br><span class="line">                    <span class="comment">// 同步请求头数据，cookie</span></span><br><span class="line">                    <span class="type">String</span> <span class="variable">cookie</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Cookie&quot;</span>);</span><br><span class="line">                    <span class="comment">// 给新请求同步老请求的Cookie</span></span><br><span class="line">                    requestTemplate.header(<span class="string">&quot;Cookie&quot;</span>,cookie);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>查询订单详情方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line">   <span class="keyword">public</span> OrderVo <span class="title function_">getOrderInfo</span><span class="params">()</span> <span class="keyword">throws</span> ExecutionException, InterruptedException &#123;</span><br><span class="line">       <span class="comment">// 获取之前的请求</span></span><br><span class="line">       <span class="type">RequestAttributes</span> <span class="variable">requestAttributes</span> <span class="operator">=</span> RequestContextHolder.getRequestAttributes();</span><br><span class="line">       CompletableFuture&lt;OrderVo&gt; cartFutrue = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">           <span class="comment">// 每一个线程都共享之前的请求</span></span><br><span class="line">           RequestContextHolder.setRequestAttributes(requestAttributes);</span><br><span class="line">           <span class="comment">// 获取购物车信息</span></span><br><span class="line">           BoundHashOperations&lt;String, Object, Object&gt; boundHashOps =</span><br><span class="line">                   redisTemplate.boundHashOps(<span class="string">&quot;gulimall:cart:1&quot;</span>);</span><br><span class="line">           List&lt;Object&gt; values = boundHashOps.values();</span><br><span class="line">           List&lt;OrderVo&gt; collect = values.stream().map(item -&gt; JSON.parseObject((String) item, OrderVo.class)).collect(Collectors.toList());</span><br><span class="line">           <span class="type">OrderVo</span> <span class="variable">orderVo</span> <span class="operator">=</span> collect.get(<span class="number">0</span>);</span><br><span class="line">           <span class="keyword">return</span> orderVo;</span><br><span class="line">       &#125;, executor);</span><br><span class="line"></span><br><span class="line">       CompletableFuture&lt;OrderVo&gt; memberFuture = cartFutrue.thenApplyAsync((reslut) -&gt; &#123;</span><br><span class="line">           <span class="comment">// 每一个线程都共享之前的请求属性</span></span><br><span class="line">           RequestContextHolder.setRequestAttributes(requestAttributes);</span><br><span class="line">           <span class="comment">// 远程调用获取地址</span></span><br><span class="line">           <span class="type">R</span> <span class="variable">info</span> <span class="operator">=</span> memberFeign.info(<span class="number">1L</span>);</span><br><span class="line">           <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> info.get(<span class="string">&quot;memberReceiveAddress&quot;</span>);</span><br><span class="line">           <span class="type">MemberReceiveAddressEntity</span> <span class="variable">memberReceiveAddress</span> <span class="operator">=</span> JSON.parseObject((String) o, MemberReceiveAddressEntity.class);</span><br><span class="line">           reslut.setAddress(memberReceiveAddress.getProvince());</span><br><span class="line">           <span class="keyword">return</span> reslut;</span><br><span class="line">       &#125;, executor);</span><br><span class="line">       <span class="type">OrderVo</span> <span class="variable">orderVo</span> <span class="operator">=</span> memberFuture.get();</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 防重令牌</span></span><br><span class="line">       <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString().replace(<span class="string">&quot;_&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">       <span class="comment">// 将令牌存入Redis</span></span><br><span class="line">       redisTemplate.opsForValue().set(<span class="string">&quot;order:userId:1&quot;</span>,token);</span><br><span class="line">       orderVo.setOrderToken(token);</span><br><span class="line"></span><br><span class="line">       <span class="keyword">return</span> orderVo;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<h4 id="幂等性"><a href="#幂等性" class="headerlink" title="幂等性"></a>幂等性</h4><blockquote>
<p>什么是幂等性</p>
</blockquote>
<p>接口幂等性：用户对同一操作发起的一次请求或<strong>多次请求的结果是一致</strong>的，<strong>不会因为多次点击而产生副作用</strong>。</p>
<blockquote>
<p>哪些情况需要防止</p>
</blockquote>
<ul>
<li><p>用户多次点击按钮</p>
</li>
<li><p>用户页面回退再次提交</p>
</li>
<li><p>微服务互相调用，由于网络问题，导致请求 失败。feign触发重试机制</p>
</li>
</ul>
<blockquote>
<p>什么情况需要幂等</p>
</blockquote>
<p>以SQL为例，有些操作天然幂等的</p>
<p>select * from user where id&#x3D;1; 无论执行多少次都不会改变，是天然幂等。</p>
<p>update user set name&#x3D;’fds’ where id&#x3D;2; 无论执行多少次，结果都是一致的，也是幂等的</p>
<p>DELETE from User where id&#x3D;1; 多次操作，结果一样，具备幂等性</p>
<p>insert into user(name,age) values(‘d’,22);   如果name为主键，，即重复操作上面的业务，只会插入一条用户数据，具备幂等性</p>
<hr>
<p>update user set num&#x3D;num+1 where id&#x3D;2; 每次执行的结果都会变化，不是幂等的。</p>
<p>insert into user(id,name) values(1,”123”)   如id不是主键，可以重复，那上面业务多次操作，数据都会新增多条，不具备幂等性。</p>
<blockquote>
<p>幂等性解决方案</p>
</blockquote>
<ul>
<li><p>token机制</p>
<ol>
<li>服务端提供了发送token接口，分析业务的时候，哪些业务是存在幂等的，就必须在执行业务前，先去获取token，服务器会把token保存在Redis中。</li>
<li>然后调用业务接口请求时，把token携带过去，一般放在请求头部。</li>
<li>服务器判断token是否存在Redis中，<strong>存在表示第一次请求，然后删除token</strong>，继续执行业务。</li>
<li>如果token不存在Redis中，就表示重复操作，直接返回重复标记给Client，这样就保证了业务代码，不被重复执行。</li>
</ol>
<p>危险性：</p>
<ol>
<li><p>先删除token，还是后删除token：先删除可能导致，业务确实没有执行，重试还带上之前的token，由于防重设计</p>
</li>
<li><p>后删除可能导致业务处理成功，但是服务闪断，出现超时，没有删除token，别人继续重试，导致业务被执行两遍</p>
</li>
<li><p>token的获取、比较、删除必须是原子性</p>
<p>可以在Redis使用lua脚本完成</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> redis.call(<span class="string">&#x27;get&#x27;</span>, KEYS[<span class="number">1</span>]) == ARGV[<span class="number">1</span>] then <span class="keyword">return</span> redis.call(<span class="string">&#x27;del&#x27;</span>, KEYS[<span class="number">1</span>]) <span class="keyword">else</span> <span class="keyword">return</span> <span class="number">0</span> end</span><br></pre></td></tr></table></figure></li>
</ol>
</li>
<li><p>各种锁机制</p>
<p>数据库悲观锁</p>
<p>数据库乐观锁</p>
<p>业务层分布式锁</p>
</li>
<li><p>各种唯一约束</p>
<p>数据库唯一约束</p>
<p>Redis set 防重</p>
<ul>
<li><p>防重表</p>
</li>
<li><p>全局请求唯一ID：调用接口时，生成一个唯一ID，Redis将数据保存到集合中(去重),存在即处理过，可以使用Nginx设置每一个请求的唯一ID</p>
</li>
</ul>
</li>
</ul>
<h4 id="延时队列（实现定时任务）"><a href="#延时队列（实现定时任务）" class="headerlink" title="延时队列（实现定时任务）"></a>延时队列（实现定时任务）</h4><blockquote>
<p>场景</p>
</blockquote>
<p>比如：未付款订单，超过一定时间后，系统自动取消订单并释放占有的物品</p>
<p>常用解决方案：</p>
<p>Spring的schedule定时任务轮训数据库</p>
<ul>
<li>缺点：消耗系统内存，增加了数据库压力，有较大的时间误差</li>
<li>解决：<strong>RabbitMQ的消息TTL和死信交换机结合</strong></li>
</ul>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231116200047885.png" alt="image-20231116200047885"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231116200125152.png" alt="image-20231116200125152"></p>
<p>TTL是<strong>消息的存活时间</strong></p>
<p>RabbitMQ可以对<strong>队列</strong>和<strong>消息</strong>分别设置TTL</p>
<ul>
<li>对队列设置TTL就是队列没有消费者连着的保留时间，超过时间，就认为这个消息死了，称之为死信</li>
</ul>
<h4 id="库存解锁服务"><a href="#库存解锁服务" class="headerlink" title="库存解锁服务"></a>库存解锁服务</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E6%B5%81%E7%A8%8B.jpg" alt="消息队列流程"></p>
<h5 id="库存服务RabbitMQ配置"><a href="#库存服务RabbitMQ配置" class="headerlink" title="库存服务RabbitMQ配置"></a>库存服务RabbitMQ配置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyRabbitConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 使用JSON序列化</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MessageConverter <span class="title function_">messageConverter</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Jackson2JsonMessageConverter</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> TopicExchange <span class="title function_">topicExchange</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TopicExchange</span>(<span class="string">&quot;stock-event-exchange&quot;</span>,<span class="literal">true</span>,<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Queue <span class="title function_">delayQueue</span><span class="params">()</span>&#123;</span><br><span class="line">        HashMap&lt;String, Object&gt; args = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-exchange&quot;</span>,<span class="string">&quot;stock-event-exchange&quot;</span>);</span><br><span class="line">        args.put(<span class="string">&quot;x-dead-letter-routing-key&quot;</span>,<span class="string">&quot;stock.release&quot;</span>);</span><br><span class="line">        args.put(<span class="string">&quot;x-message-ttl&quot;</span>,<span class="number">60000</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Queue</span>(<span class="string">&quot;stock.delay.queue&quot;</span>,<span class="literal">true</span>,<span class="literal">false</span>,<span class="literal">false</span>,args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Queue <span class="title function_">releaseQueue</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Queue</span>(<span class="string">&quot;stock.release.stock.queue&quot;</span>,<span class="literal">true</span>,<span class="literal">false</span>,<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Binding <span class="title function_">delayBing</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(delayQueue()).to(topicExchange()).with(<span class="string">&quot;stock.locked&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Binding <span class="title function_">releaseBing</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> BindingBuilder.bind(releaseQueue()).to(topicExchange()).with(<span class="string">&quot;stock.release&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="订单服务RabbitMQ配置"><a href="#订单服务RabbitMQ配置" class="headerlink" title="订单服务RabbitMQ配置"></a>订单服务RabbitMQ配置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> OrderSubmitResponseVo <span class="title function_">submitOrder</span><span class="params">(OrderSubmitVo submitVo)</span> &#123;</span><br><span class="line">        <span class="type">OrderSubmitResponseVo</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OrderSubmitResponseVo</span>();</span><br><span class="line">        <span class="comment">// 验令牌,创建订单，验价格、锁库存</span></span><br><span class="line">        <span class="comment">// 1. 验证令牌</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">orderToken</span> <span class="operator">=</span> submitVo.getOrderToken();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 原子验证令牌，0代表令牌验证失败，1代表删除成功</span></span><br><span class="line">        String script=<span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then return redis.call(&#x27;del&#x27;, KEYS[1]) else return 0 end&quot;</span>;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redisTemplate.execute(<span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;Long&gt;(script, Long.class),</span><br><span class="line">                Arrays.asList(redisTemplate.opsForValue().get(<span class="string">&quot;order:userId:1&quot;</span>)), orderToken);</span><br><span class="line">        <span class="keyword">if</span>(result==<span class="number">1L</span>)&#123;</span><br><span class="line">            <span class="comment">// 验证失败</span></span><br><span class="line">            System.out.println(<span class="string">&quot;验证失败&quot;</span>);</span><br><span class="line">            response.setCode(<span class="number">1</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 验证成功</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 1.创建订单</span></span><br><span class="line">            <span class="type">OrderCreateTo</span> <span class="variable">order</span> <span class="operator">=</span> createOrder(submitVo);</span><br><span class="line">            saveOrder(order);</span><br><span class="line">            <span class="comment">// 调用远程库存服务,此处省略</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 创建订单成功，发送到MQ中</span></span><br><span class="line">            rabbitTemplate.convertAndSend(<span class="string">&quot;order-event-exchange&quot;</span>,<span class="string">&quot;order.create.order&quot;</span>,order);</span><br><span class="line">            response.setOrder(order.getOrder());</span><br><span class="line">            response.setCode(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h5 id="创建订单"><a href="#创建订单" class="headerlink" title="创建订单"></a>创建订单</h5><p>给MQ发送消息routing key表示订单已经创建</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> OrderSubmitResponseVo <span class="title function_">submitOrder</span><span class="params">(OrderSubmitVo submitVo)</span> &#123;</span><br><span class="line">        <span class="type">OrderSubmitResponseVo</span> <span class="variable">response</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OrderSubmitResponseVo</span>();</span><br><span class="line">        <span class="comment">// 验令牌,创建订单，验价格、锁库存</span></span><br><span class="line">        <span class="comment">// 1. 验证令牌</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">orderToken</span> <span class="operator">=</span> submitVo.getOrderToken();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 原子验证令牌，0代表令牌验证失败，1代表删除成功</span></span><br><span class="line">        String script=<span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then return redis.call(&#x27;del&#x27;, KEYS[1]) else return 0 end&quot;</span>;</span><br><span class="line">        <span class="type">Long</span> <span class="variable">result</span> <span class="operator">=</span> redisTemplate.execute(<span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;Long&gt;(script, Long.class),</span><br><span class="line">                Arrays.asList(redisTemplate.opsForValue().get(<span class="string">&quot;order:userId:1&quot;</span>)), orderToken);</span><br><span class="line">        <span class="keyword">if</span>(result==<span class="number">1L</span>)&#123;</span><br><span class="line">            <span class="comment">// 验证失败</span></span><br><span class="line">            System.out.println(<span class="string">&quot;验证失败&quot;</span>);</span><br><span class="line">            response.setCode(<span class="number">1</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 验证成功</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 1.创建订单</span></span><br><span class="line">            <span class="type">OrderCreateTo</span> <span class="variable">order</span> <span class="operator">=</span> createOrder(submitVo);</span><br><span class="line">            saveOrder(order);</span><br><span class="line">            <span class="comment">// 调用远程锁库存服务,此处省略</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 创建订单成功，发送到MQ中</span></span><br><span class="line">            rabbitTemplate.convertAndSend(<span class="string">&quot;order-event-exchange&quot;</span>,<span class="string">&quot;order.create.order&quot;</span>,order);</span><br><span class="line">            response.setOrder(order.getOrder());</span><br><span class="line">            response.setCode(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h5 id="锁库存"><a href="#锁库存" class="headerlink" title="锁库存"></a>锁库存</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 锁库存</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Transactional</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Boolean <span class="title function_">orderLockStock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 保存库存工作单的详情</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="type">WareOrderTaskEntity</span> <span class="variable">taskEntity</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">WareOrderTaskEntity</span>();</span><br><span class="line">        <span class="comment">// 设置订单Id</span></span><br><span class="line">        taskEntity.setOrderSn(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">        wareOrderTaskService.save(taskEntity);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 先查询是否有库存，假设有库存</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 开始锁库存</span></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 第一个代表skuId,第二个代表wareId，第三个代表要锁的数量</span></span><br><span class="line"><span class="comment">         * 成功就返回1</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        Long count=wareSkuDao.lockSkuStock(<span class="number">1</span>,<span class="number">1</span>,<span class="number">5</span>);</span><br><span class="line">        <span class="keyword">if</span> (count==<span class="number">1</span>)&#123;</span><br><span class="line">            <span class="comment">// 锁成功</span></span><br><span class="line">            Boolean skuStocked=<span class="literal">true</span>;</span><br><span class="line">            <span class="type">WareOrderTaskDetailEntity</span> <span class="variable">entity</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">WareOrderTaskDetailEntity</span>(<span class="number">1L</span>,<span class="number">1L</span>,</span><br><span class="line">                    <span class="string">&quot;手机&quot;</span>,<span class="number">5</span>, taskEntity.getId());</span><br><span class="line">            wareOrderTaskDetailService.save(entity);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 用于传输的To对象，StockLockedTo</span></span><br><span class="line">            <span class="type">StockLockedTo</span> <span class="variable">stockLockedTo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StockLockedTo</span>();</span><br><span class="line">            stockLockedTo.setId(taskEntity.getId());</span><br><span class="line">            <span class="comment">// 封装细节</span></span><br><span class="line">            <span class="type">StockDetailTo</span> <span class="variable">stockDetailTo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StockDetailTo</span>();</span><br><span class="line">            BeanUtils.copyProperties(entity,stockDetailTo);</span><br><span class="line">            <span class="comment">// 不能只发Id，防止回滚后找不到数据</span></span><br><span class="line">            stockLockedTo.setDetail(stockDetailTo);</span><br><span class="line">            <span class="comment">// TODO 告诉MQ锁成功</span></span><br><span class="line">            rabbitTemplate.convertAndSend(<span class="string">&quot;stock-event-exchange&quot;</span>,<span class="string">&quot;stock.locked&quot;</span>,stockLockedTo);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h5 id="解锁库存"><a href="#解锁库存" class="headerlink" title="解锁库存"></a>解锁库存</h5><p>订单未支付，自动解锁</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="meta">@RabbitListener(queues = &quot;stock.release.stock.queue&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StockReleaseListener</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    WareSkuDao wareSkuDao;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    WareOrderTaskService wareOrderTaskService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    WareOrderTaskDetailService wareOrderTaskDetailService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    OrderFeginService orderFeginService;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 库存自动解锁</span></span><br><span class="line"><span class="comment">     * 1. 下订单成功，库存锁定成功，接下来的业务调用失败，导致订单回滚，之前锁定的库存就要自动解锁</span></span><br><span class="line"><span class="comment">     * 2. 订单失败，锁库存失败</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * 只要解锁库存解锁消息失败，一定要告诉服务解锁失败</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> stockLockedTo</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> message</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@RabbitHandler</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">releaseStock</span><span class="params">(StockLockedTo stockLockedTo, Message message, Channel channel)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;收到解锁的库存的消息&quot;</span>);</span><br><span class="line">        <span class="type">Long</span> <span class="variable">id</span> <span class="operator">=</span> stockLockedTo.getId(); <span class="comment">// 库存工作单Id</span></span><br><span class="line">        <span class="type">StockDetailTo</span> <span class="variable">detail</span> <span class="operator">=</span> stockLockedTo.getDetail();</span><br><span class="line">        <span class="type">Long</span> <span class="variable">skuId</span> <span class="operator">=</span> detail.getSkuId();</span><br><span class="line">        <span class="type">Long</span> <span class="variable">detailId</span> <span class="operator">=</span> detail.getId();</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 解锁：查询数据库关于这个订单的锁定库存信息</span></span><br><span class="line"><span class="comment">         * 1. 有，证明库存锁定成功，需要解锁</span></span><br><span class="line"><span class="comment">         *    没有这个订单：必须解锁</span></span><br><span class="line"><span class="comment">         *    有这个订单：不是解锁库存</span></span><br><span class="line"><span class="comment">         *          订单状态：已取消，解锁库存</span></span><br><span class="line"><span class="comment">         *                  没取消，不能解锁库存</span></span><br><span class="line"><span class="comment">         * 2. 没有，库存锁定失败，库存回滚了，这种情况无需回滚。</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="type">WareOrderTaskDetailEntity</span> <span class="variable">byId</span> <span class="operator">=</span> wareOrderTaskDetailService.getById(detailId);</span><br><span class="line">        <span class="keyword">if</span> (byId!=<span class="literal">null</span>)&#123;</span><br><span class="line">            <span class="comment">// 解锁</span></span><br><span class="line">            <span class="type">Long</span> <span class="variable">id1</span> <span class="operator">=</span> stockLockedTo.getId();</span><br><span class="line">            <span class="type">WareOrderTaskEntity</span> <span class="variable">taskEntity</span> <span class="operator">=</span> wareOrderTaskService.getById(id1);</span><br><span class="line">            <span class="type">String</span> <span class="variable">orderSn</span> <span class="operator">=</span> taskEntity.getOrderSn(); <span class="comment">// 根据订单号，查询订单状态</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 远程获取订单状态</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">orderStauts</span> <span class="operator">=</span> orderFeginService.getOrderStauts(orderSn);</span><br><span class="line">            <span class="type">OrderVo</span> <span class="variable">data</span> <span class="operator">=</span> JSON.parseObject(orderStauts, OrderVo.class);</span><br><span class="line">            System.out.println(<span class="string">&quot;远程订单返回状态&quot;</span>+data);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (data==<span class="literal">null</span>||data.getStatus()==<span class="number">4</span>)&#123;</span><br><span class="line">                <span class="comment">// 订单已取消或不存在，才能解锁库存</span></span><br><span class="line">                unLockStock(skuId,detail.getSkuNum(),detailId);</span><br><span class="line">                <span class="comment">// Rabbit手动确认</span></span><br><span class="line">                channel.basicAck(message.getMessageProperties().getDeliveryTag(),<span class="literal">false</span>);</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                <span class="comment">// Rabbit手动拒绝，重新放回队列，让别人继续消费</span></span><br><span class="line">                channel.basicReject(message.getMessageProperties().getDeliveryTag(),<span class="literal">true</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 无需解锁</span></span><br><span class="line">            <span class="comment">// Rabbit手动确认</span></span><br><span class="line">            channel.basicAck(message.getMessageProperties().getDeliveryTag(),<span class="literal">false</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">unLockStock</span><span class="params">(Long skuId,Integer num,Long taskDetailsId)</span>&#123;</span><br><span class="line">        <span class="comment">// 例如：原来的库存减了4，现在再加回去</span></span><br><span class="line">        Long status=wareSkuDao.unLockStock(skuId,num,taskDetailsId);</span><br><span class="line">        System.out.println(<span class="string">&quot;订单状态：&quot;</span>+status);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     *  防止卡顿订单，永远不能解锁库存</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@RabbitHandler</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">releaseStock</span><span class="params">(OrderEntityTo orderEntityTo, Message message, Channel channel)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;订单关闭，准备解锁库存&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 防止订单卡顿，导致订单状态一直改不了，库存消息优先到期，查询订单状态为新建状态，</span></span><br><span class="line">            wareSkuService.unLockStock(orderEntityTo);</span><br><span class="line">            <span class="comment">// Rabbit手动确认</span></span><br><span class="line">            channel.basicAck(message.getMessageProperties().getDeliveryTag(), <span class="literal">false</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="comment">// Rabbit手动拒绝，重新放回队列，让别人继续消费</span></span><br><span class="line">            channel.basicReject(message.getMessageProperties().getDeliveryTag(), <span class="literal">true</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h4 id="定时关单功能"><a href="#定时关单功能" class="headerlink" title="定时关单功能"></a>定时关单功能</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120091751331.png" alt="image-20231120091751331"></p>
<h5 id="订单释放-库存解锁"><a href="#订单释放-库存解锁" class="headerlink" title="订单释放&amp;库存解锁"></a>订单释放&amp;库存解锁</h5><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120104632345.png" alt="image-20231120104632345"></p>
<p>上面是正常情况，下面的是异常的</p>
<p><strong>如果订单解锁卡顿30Min，此时，库存解锁失败，库存解锁消息消费完了，之后订单取消。修改订单状态，库存一致得不到释放。</strong></p>
<blockquote>
<p>解决办法：</p>
</blockquote>
<p>订单释放后再发送一个routing key，最后再进入库存解锁服务。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120105820982.png" alt="image-20231120105820982"></p>
<h3 id="秒杀服务"><a href="#秒杀服务" class="headerlink" title="秒杀服务"></a>秒杀服务</h3><h4 id="秒杀特点："><a href="#秒杀特点：" class="headerlink" title="秒杀特点："></a>秒杀特点：</h4><p>瞬时高并发、必须要做<strong>限流</strong>+<strong>异步</strong>+<strong>缓存</strong>(页面静态化)+<strong>独立部署</strong></p>
<h4 id="限流方式"><a href="#限流方式" class="headerlink" title="限流方式"></a>限流方式</h4><ol>
<li>前端限流，一些高并发的网站直接在前端页面开始限流，例如：小米的验证码设计</li>
<li>nginx限流，直接负载部分请求到错误的静态页面：令牌算法 漏斗算法</li>
<li>网关限流，限流的过滤器。或者使用专业的限流组件sentinel</li>
<li>代码中使用分布式信号量</li>
<li>rabbitmq限流（能者多劳：chanel.basicQos(1)），保证发挥所有服务器的性能。</li>
</ol>
<h4 id="定时任务"><a href="#定时任务" class="headerlink" title="定时任务"></a>定时任务</h4><blockquote>
<p>cron表达式</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120151952997.png" alt="image-20231120151952997"></p>
<p>也可以<strong>在线生成工具快速生成cron表达式</strong>。</p>
<h5 id="SpringBoot整合定时任务"><a href="#SpringBoot整合定时任务" class="headerlink" title="SpringBoot整合定时任务"></a>SpringBoot整合定时任务</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@EnableScheduling</span></span><br><span class="line"><span class="meta">@EnableAsync</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloSchedule</span> &#123;</span><br><span class="line">    <span class="comment">/**定时任务</span></span><br><span class="line"><span class="comment">     * 1. spring中只允许有6位，不能有年</span></span><br><span class="line"><span class="comment">     * 2. Spring中周一到周日为，1~7，corn中则是周日到周六为1~7</span></span><br><span class="line"><span class="comment">     * 3. 定时任务不应该阻塞，默认是阻塞的，等待前一个任务执行完，后一个再执行</span></span><br><span class="line"><span class="comment">     *    解决方法： 1. 让业务以异步的形式，自己提交的线程池</span></span><br><span class="line"><span class="comment">     *             2. 定时任务线程池，默认为1,配置文件中设置，有时候会因为版本问题不生效</span></span><br><span class="line"><span class="comment">     *          spring:</span></span><br><span class="line"><span class="comment">     *            task:</span></span><br><span class="line"><span class="comment">     *              scheduling:</span></span><br><span class="line"><span class="comment">     *                      pool:</span></span><br><span class="line"><span class="comment">     *                          size: 5</span></span><br><span class="line"><span class="comment">     *             3. 异步任务</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**异步任务</span></span><br><span class="line"><span class="comment">     * 1. 类上加<span class="doctag">@EnableAsync</span></span></span><br><span class="line"><span class="comment">     * 2. 方法上加<span class="doctag">@Async</span></span></span><br><span class="line"><span class="comment">     * 3. 在配置文件添加配置</span></span><br><span class="line"><span class="comment">     *    spring:</span></span><br><span class="line"><span class="comment">     *        task:</span></span><br><span class="line"><span class="comment">     *          execution:</span></span><br><span class="line"><span class="comment">     *                  pool:</span></span><br><span class="line"><span class="comment">     *                      core-size: 5</span></span><br><span class="line"><span class="comment">     *                      max-size: 50</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Async</span></span><br><span class="line">    <span class="meta">@Scheduled(cron =&quot;* * * ? * 1&quot; )</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">hello</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="查询最近三天的活动"><a href="#查询最近三天的活动" class="headerlink" title="查询最近三天的活动"></a>查询最近三天的活动</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;select id=<span class="string">&quot;getLast3DaysSession&quot;</span> resultType=<span class="string">&quot;com.atguigu.gulimall.coupon.entity.SeckillSessionEntity&quot;</span>&gt;</span><br><span class="line">        SELECT * FROM sms_seckill_session</span><br><span class="line">                 WHERE <span class="title function_">DATE_SUB</span><span class="params">(CURDATE()</span>, INTERVAL <span class="number">3</span> DAY) &amp;lt; end_time;</span><br><span class="line">    &lt;/select&gt;</span><br></pre></td></tr></table></figure>



<h4 id="秒杀商品上架"><a href="#秒杀商品上架" class="headerlink" title="秒杀商品上架"></a>秒杀商品上架</h4><p><strong><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120165013148.png" alt="image-20231120165013148"></strong></p>
<h5 id="秒杀定时设置"><a href="#秒杀定时设置" class="headerlink" title="秒杀定时设置"></a>秒杀定时设置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecKillSkuScheduled</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    SecKillService secKillService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String upload_lock=<span class="string">&quot;seckill:upload:lock&quot;</span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 秒杀商品上架</span></span><br><span class="line"><span class="comment">     * 每天晚上3点，上架最近三天的商品</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Scheduled(cron = &quot;* * * * * ?&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">uploadSecKillSku3Days</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 重复上架无需处理</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 分布式锁，锁的业务执行完成，状态更新完成，释放锁之后，其他人获取到最新状态</span></span><br><span class="line">        <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(upload_lock);</span><br><span class="line">        lock.lock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            secKillService.uploadSecKillSku3Days();</span><br><span class="line">        &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;商品上架成功&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>详细设置</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SecKillServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">SecKillService</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    CouponFeign couponFeign;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    StringRedisTemplate redisTemplate;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    ProductFeign productFeign;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    RedissonClient redissonClient;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String SESSION_CACHE_FREFIX=<span class="string">&quot;seckill:session:&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String SKU_STOCK_SEMPHORE=<span class="string">&quot;seckill:stock:&quot;</span>; <span class="comment">// 后面+随机码</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">uploadSecKillSku3Days</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 扫描最近三天需要参加秒杀的活动</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="type">R</span> <span class="variable">last3DaysSession</span> <span class="operator">=</span> couponFeign.getLast3DaysSession();</span><br><span class="line">        <span class="type">Object</span> <span class="variable">data</span> <span class="operator">=</span> last3DaysSession.get(<span class="string">&quot;data&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (data!=<span class="literal">null</span>)&#123;</span><br><span class="line">            <span class="comment">// 参加活动的商品</span></span><br><span class="line">            List&lt;SeckillSessionWithSkusVo&gt; secKillSessionWithSkus = JSON.parseObject((String) data,</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;List&lt;SeckillSessionWithSkusVo&gt;&gt;() &#123;&#125;);</span><br><span class="line">            <span class="comment">/**缓存到Redis</span></span><br><span class="line"><span class="comment">             * 1. 缓存活动信息</span></span><br><span class="line"><span class="comment">             * 2. 缓存活动关联商品信息</span></span><br><span class="line"><span class="comment">              */</span></span><br><span class="line">            <span class="comment">//缓存活动信息</span></span><br><span class="line">            saveSessionInfos(secKillSessionWithSkus);</span><br><span class="line">            <span class="comment">//缓存活动关联商品信息</span></span><br><span class="line">            saveSessionSkuInfo(secKillSessionWithSkus);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">saveSessionInfos</span><span class="params">(List&lt;SeckillSessionWithSkusVo&gt; secKillSessionWithSkus)</span> &#123;</span><br><span class="line">        secKillSessionWithSkus.forEach(session-&gt;&#123;</span><br><span class="line">            <span class="comment">// 时间比较用Long比较合适</span></span><br><span class="line">            <span class="type">Long</span> <span class="variable">startTime</span> <span class="operator">=</span> session.getStartTime().getTime();</span><br><span class="line">            <span class="type">Long</span> <span class="variable">endTime</span> <span class="operator">=</span> session.getEndTime().getTime();</span><br><span class="line">            String key=startTime+<span class="string">&quot;_&quot;</span>+endTime;</span><br><span class="line">            <span class="keyword">if</span> (session.getSkuRelationEntities()!=<span class="literal">null</span>)&#123;</span><br><span class="line">                List&lt;Long&gt; skuIds = session.getSkuRelationEntities().stream().map(SeckillSkuVo::getSkuId).collect(Collectors.toList());</span><br><span class="line">                <span class="comment">// 缓存活动信息</span></span><br><span class="line">                <span class="keyword">if</span> (skuIds!=<span class="literal">null</span>&amp;&amp; !skuIds.isEmpty())&#123;</span><br><span class="line">                    <span class="comment">// 幂等性保证,如果Redis中这个key（活动开始和结束时间）</span></span><br><span class="line">                    <span class="keyword">if</span> (!redisTemplate.hasKey(key))&#123;</span><br><span class="line">                        redisTemplate.opsForValue().set(SESSION_CACHE_FREFIX+key, String.valueOf(skuIds));</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">saveSessionSkuInfo</span><span class="params">(List&lt;SeckillSessionWithSkusVo&gt; sessions)</span> &#123;</span><br><span class="line"></span><br><span class="line">        sessions.forEach(session -&gt; &#123;</span><br><span class="line">            <span class="comment">//准备hash操作，绑定hash</span></span><br><span class="line">            BoundHashOperations&lt;String, Object, Object&gt; operations = redisTemplate.boundHashOps(SESSION_CACHE_FREFIX);</span><br><span class="line">            session.getSkuRelationEntities().forEach(seckillSkuVo -&gt; &#123;</span><br><span class="line">                <span class="comment">//生成随机码</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> UUID.randomUUID().toString().replace(<span class="string">&quot;-&quot;</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line">                <span class="type">String</span> <span class="variable">redisKey</span> <span class="operator">=</span> seckillSkuVo.getPromotionSessionId().toString() + <span class="string">&quot;-&quot;</span> + seckillSkuVo.getSkuId().toString();</span><br><span class="line">                <span class="comment">// 幂等性保证,如果Redis中已经有这个商品信息，就不再添加</span></span><br><span class="line">                <span class="keyword">if</span> (Boolean.FALSE.equals(operations.hasKey(redisKey))) &#123;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">//缓存我们商品信息</span></span><br><span class="line">                    <span class="type">SeckillSkuRedisTo</span> <span class="variable">redisTo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SeckillSkuRedisTo</span>();</span><br><span class="line">                    <span class="type">Long</span> <span class="variable">skuId</span> <span class="operator">=</span> seckillSkuVo.getSkuId();</span><br><span class="line">                    <span class="comment">//1、先查询sku的基本信息，调用远程服务</span></span><br><span class="line">                    <span class="type">R</span> <span class="variable">info</span> <span class="operator">=</span> productFeign.info(skuId);</span><br><span class="line">                    <span class="type">Object</span> <span class="variable">data</span> <span class="operator">=</span> info.get(<span class="string">&quot;data&quot;</span>);</span><br><span class="line">                    <span class="type">SkuInfoVo</span> <span class="variable">skuInfoVo</span> <span class="operator">=</span> JSON.parseObject((String) data, <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;SkuInfoVo&gt;() &#123;</span><br><span class="line">                    &#125;);</span><br><span class="line">                    redisTo.setSkuInfo(skuInfoVo);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                    <span class="comment">//2、sku的秒杀信息</span></span><br><span class="line">                    BeanUtils.copyProperties(seckillSkuVo,redisTo);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">//3、设置当前商品的秒杀时间信息</span></span><br><span class="line">                    redisTo.setStartTime(session.getStartTime().getTime());</span><br><span class="line">                    redisTo.setEndTime(session.getEndTime().getTime());</span><br><span class="line"></span><br><span class="line">                    <span class="comment">//4、设置商品的随机码（防止恶意攻击）</span></span><br><span class="line">                    redisTo.setRandomCode(token);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                    <span class="comment">//序列化json格式存入Redis中</span></span><br><span class="line">                    <span class="type">String</span> <span class="variable">seckillValue</span> <span class="operator">=</span> JSON.toJSONString(redisTo);</span><br><span class="line">                    operations.put(seckillSkuVo.getPromotionSessionId().toString() + <span class="string">&quot;-&quot;</span> + seckillSkuVo.getSkuId().toString(),seckillValue);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 如果当前场次的活动的库存信息已经上架，就不再上架</span></span><br><span class="line">                    <span class="type">RSemaphore</span> <span class="variable">semaphore</span> <span class="operator">=</span> redissonClient.getSemaphore(SKU_STOCK_SEMPHORE + token);</span><br><span class="line">                    <span class="comment">// 商品可以秒杀的数量作为信号量</span></span><br><span class="line">                    semaphore.trySetPermits(seckillSkuVo.getSeckillCount());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="幂等性设计"><a href="#幂等性设计" class="headerlink" title="幂等性设计"></a>幂等性设计</h5><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121090812657.png" alt="image-20231121090812657"></p>
<p>加一个分布式锁+幂等性判断(判断Redis是否已经存在该数据)</p>
<h5 id="查询秒杀商品"><a href="#查询秒杀商品" class="headerlink" title="查询秒杀商品"></a>查询秒杀商品</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回可以当前时间可以返回的秒杀商品信息</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> List&lt;SeckillSkuRedisTo&gt; <span class="title function_">getCurrentSeckillSkus</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 获取当前时间属于哪个秒杀场次</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">time</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>().getTime();</span><br><span class="line">        Set&lt;String&gt; keys = redisTemplate.keys(SESSION_CACHE_FREFIX + <span class="string">&quot;*&quot;</span>);</span><br><span class="line">        <span class="keyword">for</span> (String key : keys) &#123;</span><br><span class="line">            <span class="comment">//seckill:session:1700467735000_1700665743000</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">replace</span> <span class="operator">=</span> key.replace(SESSION_CACHE_FREFIX, <span class="string">&quot;&quot;</span>);</span><br><span class="line">            <span class="comment">// 分割后获取两个时间</span></span><br><span class="line">            String[] s = replace.split(<span class="string">&quot;_&quot;</span>);</span><br><span class="line">            <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> Long.parseLong(s[<span class="number">0</span>]);</span><br><span class="line">            <span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> Long.parseLong(s[<span class="number">1</span>]);</span><br><span class="line">            <span class="comment">// 获取到当前场次</span></span><br><span class="line">            <span class="keyword">if</span> (time&gt;=startTime&amp;&amp;time&lt;=endTime)&#123;</span><br><span class="line">                <span class="comment">// 2. 获取秒杀场次需要的商品信息,-100到100表示拿出这个key对应的所有值，100必须闭这个keys数组长度大</span></span><br><span class="line">                <span class="comment">// [1-26,2-38]</span></span><br><span class="line">                List&lt;String&gt; range = redisTemplate.opsForList().range(key, -<span class="number">100</span>, <span class="number">100</span>);</span><br><span class="line">                System.out.println(range);</span><br><span class="line">                BoundHashOperations&lt;String, String, Object&gt; boundHashOps = redisTemplate.boundHashOps(<span class="string">&quot;seckill:skus&quot;</span>);</span><br><span class="line">                List&lt;Object&gt; list = boundHashOps.multiGet(range);</span><br><span class="line">                <span class="keyword">if</span> (list!=<span class="literal">null</span>)&#123;</span><br><span class="line">                    List&lt;SeckillSkuRedisTo&gt; collect = list.stream().map(item -&gt; &#123;</span><br><span class="line">                        <span class="comment">// 注意随机码，不能返回</span></span><br><span class="line">                        <span class="keyword">return</span> JSON.parseObject((String) item, SeckillSkuRedisTo.class);</span><br><span class="line">                    &#125;).collect(Collectors.toList());</span><br><span class="line">                    <span class="keyword">return</span> collect;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h5 id="秒杀系统设计"><a href="#秒杀系统设计" class="headerlink" title="秒杀系统设计"></a>秒杀系统设计</h5><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121103327462.png" alt="image-20231121103327462"></p>
<p>过滤静态请求（CDN网络，采用阿里云分散压力）。</p>
<p>秒杀链接加密（随机码）,避免恶意攻击</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121103644763.png" alt="image-20231121103644763"></p>
<p>网关层拦截恶意请求</p>
<p>引入验证码机制、加入购物车，将流量分担到更大宽度的时间点。</p>
<p>熔断：调用其它服务失败，快速返回失败</p>
<p>降级：流量太大，转到另一个页面，说明当前流量过大</p>
<p>队列削峰：所有秒杀成功的请求，先进入队列，慢慢创建订单。可以说已经抢到，五分钟之后再来看。</p>
<h5 id="登录检查"><a href="#登录检查" class="headerlink" title="登录检查"></a>登录检查</h5><p>检测当前用户是否登录</p>
<p>拦截器</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.xunqi.common.vo.MemberResponseVo;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.AntPathMatcher;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.servlet.HandlerInterceptor;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.servlet.ModelAndView;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletResponse;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpSession;</span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="keyword">static</span> com.xunqi.common.constant.AuthServerConstant.LOGIN_USER;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 登录拦截器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span>: with IntelliJ IDEA.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: 夏沫止水</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2020-07-02 18:37</span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginUserInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ThreadLocal&lt;MemberResponseVo&gt; loginUser = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">uri</span> <span class="operator">=</span> request.getRequestURI();</span><br><span class="line">        <span class="type">AntPathMatcher</span> <span class="variable">antPathMatcher</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AntPathMatcher</span>();</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">match</span> <span class="operator">=</span> antPathMatcher.match(<span class="string">&quot;/kill&quot;</span>, uri);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (match) &#123;</span><br><span class="line">            <span class="type">HttpSession</span> <span class="variable">session</span> <span class="operator">=</span> request.getSession();</span><br><span class="line">            <span class="comment">//获取登录的用户信息</span></span><br><span class="line">            <span class="type">MemberResponseVo</span> <span class="variable">attribute</span> <span class="operator">=</span> (MemberResponseVo) session.getAttribute(LOGIN_USER);</span><br><span class="line">            <span class="keyword">if</span> (attribute != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">//把登录后用户的信息放在ThreadLocal里面进行保存</span></span><br><span class="line">                loginUser.set(attribute);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">//未登录，返回登录页面</span></span><br><span class="line">                response.setContentType(<span class="string">&quot;text/html;charset=UTF-8&quot;</span>);</span><br><span class="line">                <span class="type">PrintWriter</span> <span class="variable">out</span> <span class="operator">=</span> response.getWriter();</span><br><span class="line">                out.println(<span class="string">&quot;&lt;script&gt;alert(&#x27;请先进行登录，再进行后续操作！&#x27;);location.href=&#x27;http://auth.gulimall.com/login.html&#x27;&lt;/script&gt;&quot;</span>);</span><br><span class="line">                <span class="comment">// session.setAttribute(&quot;msg&quot;, &quot;请先进行登录&quot;);</span></span><br><span class="line">                <span class="comment">// response.sendRedirect(&quot;http://auth.gulimall.com/login.html&quot;);</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterCompletion</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>添加拦截器到配置中</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SeckillWebConfig</span> <span class="keyword">implements</span> <span class="title class_">WebMvcConfigurer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> LoginUserInterceptor loginUserInterceptor;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addInterceptors</span><span class="params">(InterceptorRegistry registry)</span> &#123;</span><br><span class="line">        registry.addInterceptor(loginUserInterceptor).addPathPatterns(<span class="string">&quot;/**&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>设置Cookie</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GulimallSessionConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CookieSerializer <span class="title function_">cookieSerializer</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">DefaultCookieSerializer</span> <span class="variable">cookieSerializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultCookieSerializer</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//放大作用域</span></span><br><span class="line">        cookieSerializer.setDomainName(<span class="string">&quot;gulimall.com&quot;</span>);</span><br><span class="line">        cookieSerializer.setCookieName(<span class="string">&quot;GULISESSION&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> cookieSerializer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisSerializer&lt;Object&gt; <span class="title function_">springSessionDefaultRedisSerializer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="秒杀流程"><a href="#秒杀流程" class="headerlink" title="秒杀流程"></a>秒杀流程</h5><p>两种方式：</p>
<ol>
<li>走原来的购物车流程</li>
<li>独立的业务， 不操作数据库，本文采用第二种</li>
</ol>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231121105655530.png" alt="image-20231121105655530"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120145054406.png" alt="image-20231120145054406"></p>
<p>这个队列可以用来对流量进行削峰</p>
<h4 id="秒杀代码"><a href="#秒杀代码" class="headerlink" title="秒杀代码"></a>秒杀代码</h4><h5 id="JSON序列化设置"><a href="#JSON序列化设置" class="headerlink" title="JSON序列化设置"></a>JSON序列化设置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyMQConfig</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 使用JSON序列化</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> MessageConverter <span class="title function_">messageConverter</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Jackson2JsonMessageConverter</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><span style="color:red">注意：给MQ传递对象时，传递的对象类必须实现序列化接口，上面的JSON序列化才会生效</span></p>
<h5 id="Redisson分布式设置"><a href="#Redisson分布式设置" class="headerlink" title="Redisson分布式设置"></a>Redisson分布式设置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedissonConfig</span> &#123;</span><br><span class="line">    <span class="meta">@Bean(destroyMethod = &quot;shutdown&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> RedissonClient <span class="title function_">redisson</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="type">Config</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Config</span>();</span><br><span class="line">        <span class="comment">// 可以rediss表示启动SSL安全连接</span></span><br><span class="line">        config.useSingleServer().setAddress(<span class="string">&quot;redis://127.0.0.1:6379&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> Redisson.create(config);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="Controller请求设置"><a href="#Controller请求设置" class="headerlink" title="Controller请求设置"></a>Controller请求设置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SeckillController</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    SecKillService secKillService;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 返回当前时间可以秒杀的商品</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/getCurrentSeckillSkus&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> R <span class="title function_">getCurrentSeckillSkus</span><span class="params">()</span>&#123;</span><br><span class="line">        List&lt;SeckillSkuRedisTo&gt; list=secKillService.getCurrentSeckillSkus();</span><br><span class="line">        <span class="keyword">return</span> R.ok().put(<span class="string">&quot;data&quot;</span>,list.toString());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 开始秒杀商品</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/kill&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> R <span class="title function_">secKill</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        String killId=<span class="string">&quot;1-26&quot;</span>;</span><br><span class="line">        String key=<span class="string">&quot;7cd97299558d4d97b1f56773025246e0&quot;</span>; <span class="comment">// 随机码</span></span><br><span class="line">        Integer num=<span class="number">2</span>;</span><br><span class="line">        String orderSn=secKillService.kill(killId,key,num);</span><br><span class="line">        <span class="keyword">return</span> R.ok().put(<span class="string">&quot;data&quot;</span>,orderSn);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="kill方法，合法性校验"><a href="#kill方法，合法性校验" class="headerlink" title="kill方法，合法性校验"></a>kill方法，合法性校验</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">kill</span><span class="params">(String killId, String key, Integer num)</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 获取当前秒杀商品的详细信息</span></span><br><span class="line">        BoundHashOperations&lt;String, String, String&gt; boundHashOps = redisTemplate.boundHashOps(<span class="string">&quot;seckill:skus&quot;</span>);</span><br><span class="line">        <span class="comment">// killId为1-26,2-45类似的</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> boundHashOps.get(killId);</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isNotEmpty(s))&#123;</span><br><span class="line">            <span class="type">SeckillSkuRedisTo</span> <span class="variable">skuRedisTo</span> <span class="operator">=</span> JSON.parseObject(s, SeckillSkuRedisTo.class);</span><br><span class="line">            <span class="comment">// 校验合法性</span></span><br><span class="line">            <span class="type">Long</span> <span class="variable">startTime</span> <span class="operator">=</span> skuRedisTo.getStartTime();</span><br><span class="line">            <span class="type">Long</span> <span class="variable">endTime</span> <span class="operator">=</span> skuRedisTo.getEndTime();</span><br><span class="line">            <span class="type">Long</span> <span class="variable">currentTime</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Date</span>().getTime();</span><br><span class="line">            <span class="comment">// 校验时间合法性</span></span><br><span class="line">            <span class="keyword">if</span> (currentTime&gt;=startTime&amp;&amp;currentTime&lt;=endTime)&#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">randomCode</span> <span class="operator">=</span> skuRedisTo.getRandomCode();</span><br><span class="line">                <span class="type">String</span> <span class="variable">skuId</span> <span class="operator">=</span> skuRedisTo.getPromotionSessionId() + <span class="string">&quot;-&quot;</span> + skuRedisTo.getSkuId();</span><br><span class="line">                <span class="comment">// 校验随机码和skuId(1-24)</span></span><br><span class="line">                <span class="keyword">if</span> (key.equals(randomCode)&amp;&amp;killId.equals(skuId))&#123;</span><br><span class="line">                    <span class="comment">// 验证购物数量是否合理</span></span><br><span class="line">                    <span class="keyword">if</span> (num&lt;=skuRedisTo.getSeckillLimit())&#123;</span><br><span class="line">                        <span class="comment">// 验证这个人是否已经购买过,幂等性，如果秒杀成功，就去占位。userId_sessionId_skuId</span></span><br><span class="line">                        <span class="comment">// setIfAbsent如果有，就不占位</span></span><br><span class="line">                        String redisKey=<span class="string">&quot;userId&quot;</span>+<span class="string">&quot;_&quot;</span>+skuId;</span><br><span class="line">                        <span class="comment">// 占位设置自动过期</span></span><br><span class="line">                        Long ttl=endTime-currentTime;</span><br><span class="line">                        <span class="type">Boolean</span> <span class="variable">b</span> <span class="operator">=</span> redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);</span><br><span class="line">                        <span class="keyword">if</span> (b)&#123;</span><br><span class="line">                            <span class="comment">// 占位成功表示没有买过</span></span><br><span class="line">                            <span class="type">RSemaphore</span> <span class="variable">semaphore</span> <span class="operator">=</span> redissonClient.getSemaphore(SKU_STOCK_SEMPHORE + randomCode);</span><br><span class="line">                            <span class="keyword">try</span> &#123;</span><br><span class="line">                                <span class="comment">// Acquire会一直阻塞等待，tryAcquire则是超过100毫秒就不等待</span></span><br><span class="line">                                semaphore.tryAcquire(num,<span class="number">100</span>,TimeUnit.MILLISECONDS);</span><br><span class="line">                                <span class="comment">// 秒杀成功</span></span><br><span class="line">                                System.out.println(<span class="string">&quot;秒杀成功&quot;</span>);</span><br><span class="line">                                <span class="comment">// 生成订单号</span></span><br><span class="line">                                String orderSn=UUID.randomUUID().toString();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                                <span class="comment">// 快速下单，给MQ发送消息</span></span><br><span class="line">                                <span class="comment">// 注意：SeckillOrderTo这个类对象必须实现序列化，传输过程中，JSON序列化才能生效</span></span><br><span class="line">                                <span class="type">SeckillOrderTo</span> <span class="variable">seckillOrderTo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SeckillOrderTo</span>();</span><br><span class="line">                                seckillOrderTo.setOrderSn(orderSn);</span><br><span class="line">                                seckillOrderTo.setNum(num);</span><br><span class="line">                                seckillOrderTo.setSeckillPrice(skuRedisTo.getSeckillPrice());</span><br><span class="line">                                seckillOrderTo.setSkuId(skuRedisTo.getSkuId());</span><br><span class="line">                                seckillOrderTo.setPromotionSessionId(skuRedisTo.getPromotionSessionId());</span><br><span class="line">                                rabbitTemplate.convertAndSend(<span class="string">&quot;order-event-exchange&quot;</span>,<span class="string">&quot;order.seckill.order&quot;</span>,seckillOrderTo);</span><br><span class="line"></span><br><span class="line">                                <span class="keyword">return</span> orderSn;</span><br><span class="line"></span><br><span class="line">                            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="comment">// 表示已经买过了</span></span><br><span class="line">                            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p><span sytle="color:red">注意：MQ消息没有的确认的一定要删除，否则报错</span></p>
<h3 id="Sentinel高并发–保证稳"><a href="#Sentinel高并发–保证稳" class="headerlink" title="Sentinel高并发–保证稳"></a>Sentinel高并发–保证稳</h3><h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><h5 id="限流熔断降级"><a href="#限流熔断降级" class="headerlink" title="限流熔断降级"></a>限流熔断降级</h5><ul>
<li>熔断：A调用B服务，B不稳定，如果这样的次数太多，直接将B断路，熔断：凡是调用B的直接返回降级数据，不必等待B。不会受级联影响。</li>
<li>降级：流量高峰，服务器压力大，把一些注册服务等降级或停掉，保证核心业务的运行，所有的调用直接返回降级数据。默认触发熔断方法</li>
<li>限流：对打入的请求流量进行控制</li>
</ul>
<p>异同：</p>
<ul>
<li>相同点：<ol>
<li>保证大部分服务可用性、可靠性，防止崩溃</li>
<li>用户最终体验到某个功能不可用</li>
</ol>
</li>
<li>不同点：<ol>
<li>熔断是被调用方的故障，直接返回当前服务不可用。</li>
<li>降级基于全局考虑，停掉一些正常服务，保证核心业务。</li>
</ol>
</li>
</ul>
<h4 id="Sentinel使用"><a href="#Sentinel使用" class="headerlink" title="Sentinel使用"></a>Sentinel使用</h4><ol>
<li>定义资源</li>
<li>定义规则</li>
<li>检验规则是否生效</li>
</ol>
<h4 id="Sentinel整合SpringBoot"><a href="#Sentinel整合SpringBoot" class="headerlink" title="Sentinel整合SpringBoot"></a>Sentinel整合SpringBoot</h4><p><strong>Sentinel官方文档</strong> <a target="_blank" rel="noopener" href="https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel">https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel</a> </p>
<ol>
<li>依赖</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">			&lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;</span><br><span class="line">			&lt;artifactId&gt;spring-cloud-starter-alibaba-sentinel&lt;/artifactId&gt;</span><br><span class="line">		&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li><strong>sentinel控制台</strong></li>
</ol>
<p>​	java -jar sentinel-dashboard-1.6.3   启动</p>
<p>​	localhost:8080</p>
<p>​	用户名和密码sentinel</p>
<ol start="3">
<li><p>配置控制台信息</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  cloud:</span><br><span class="line">    sentinel:</span><br><span class="line">      transport:</span><br><span class="line">        port: <span class="number">8719</span></span><br><span class="line">        dashboard: localhost:<span class="number">8080</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>导入actuator模块</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">			&lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;</span><br><span class="line">		&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">management:</span><br><span class="line">  endpoints:</span><br><span class="line">    web:</span><br><span class="line">      exposure:</span><br><span class="line">        include: <span class="string">&#x27;*&#x27;</span>  # 暴露信息，包括当前应用的所有规则信息，日志信息等</span><br></pre></td></tr></table></figure></li>
</ol>
<p><img src="C:\Users\ZC\AppData\Roaming\Typora\typora-user-images\image-20231122132817975.png" alt="image-20231122132817975"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231122134522243.png" alt="image-20231122134522243"></p>
<blockquote>
<p>添加流控(每次重启服务，要重新添加流控)</p>
</blockquote>
<p>超出返回：Blocked by Sentinel (flow limiting)</p>
<blockquote>
<p>自定义Sentinel流控返回数据</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SeckillSentinelConfig</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SeckillSentinelConfig</span><span class="params">()</span>&#123;</span><br><span class="line">        WebCallbackManager.setUrlBlockHandler(<span class="keyword">new</span> <span class="title class_">UrlBlockHandler</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">blocked</span><span class="params">(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">                <span class="type">R</span> <span class="variable">error</span> <span class="operator">=</span> R.error(<span class="number">10002</span>, <span class="string">&quot;流量超出&quot;</span>);</span><br><span class="line">                response.setCharacterEncoding(<span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">                response.setContentType(<span class="string">&quot;application/json&quot;</span>);</span><br><span class="line">                response.getWriter().write(JSON.toJSONString(error));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="Sentinel熔断和降级—-整合Feign"><a href="#Sentinel熔断和降级—-整合Feign" class="headerlink" title="Sentinel熔断和降级—-整合Feign"></a>Sentinel熔断和降级—-整合Feign</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231122141630012.png" alt="image-20231122141630012"></p>
<h5 id="调用方的熔断保护"><a href="#调用方的熔断保护" class="headerlink" title="调用方的熔断保护"></a>调用方的熔断保护</h5><blockquote>
<p>配置文件添加对远程调用的监控</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">feign:</span><br><span class="line">  sentinel:</span><br><span class="line">    enabled: <span class="literal">true</span>  # 可以添加对远程调用的监控</span><br></pre></td></tr></table></figure>

<blockquote>
<p>Feign接口添加回调类</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FeignClient(value = &quot;gulimall-coupon&quot;,fallback = SeckillFeignService.class )</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CouponFeign</span> &#123;</span><br><span class="line">    <span class="meta">@GetMapping(&quot;coupon/seckillsession/last3DaysSession&quot;)</span></span><br><span class="line">    R <span class="title function_">getLast3DaysSession</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SeckillFeignService</span> <span class="keyword">implements</span> <span class="title class_">CouponFeign</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> R <span class="title function_">getLast3DaysSession</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;熔断方法调用，Coupon远程调用失败&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> R.error(<span class="number">1000</span>,<span class="string">&quot;太多请求,Coupon远程调用失败&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h5 id="在页面手动指定远程服务的降级策略"><a href="#在页面手动指定远程服务的降级策略" class="headerlink" title="在页面手动指定远程服务的降级策略"></a>在页面手动指定远程服务的降级策略</h5><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231122140858826.png" alt="image-20231122140858826"></p>
<p>远程服务被降级处理，<strong>触发我们定义的的熔断方法</strong>。</p>
<h3 id="自定义受保护资源"><a href="#自定义受保护资源" class="headerlink" title="自定义受保护资源"></a>自定义受保护资源</h3><ol>
<li>方法一：基于代码</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 自定义的资源保护，资源名为seckillSkus，可以针对这个进行流量控制</span></span><br><span class="line">        <span class="keyword">try</span>(Entry entry=SphU.entry(<span class="string">&quot;seckillSkus&quot;</span>)) &#123;</span><br><span class="line">            <span class="comment">// 业务逻辑</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (BlockException e) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;资源限流&quot;</span>);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>方法二：基于注解</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SentinelResource(value = &quot;secKillSkus&quot;,blockHandler=.....)</span></span><br><span class="line"><span class="comment">// blockHandler出现降级调用</span></span><br></pre></td></tr></table></figure>

<h4 id="网关流控"><a href="#网关流控" class="headerlink" title="网关流控"></a>网关流控</h4><h2 id="本地事务"><a href="#本地事务" class="headerlink" title="本地事务"></a>本地事务</h2><h3 id="本地事务-1"><a href="#本地事务-1" class="headerlink" title="本地事务"></a>本地事务</h3><h4 id="事务的基本性质"><a href="#事务的基本性质" class="headerlink" title="事务的基本性质"></a>事务的基本性质</h4><ul>
<li>原子性：一系列的操作不可拆分，要么同时成功，要么同时失败</li>
<li>一致性：数据在事务前后，业务整体一直</li>
<li>隔离性：事务之间互相隔离</li>
<li>持久性：事务成功，保存到数据库</li>
</ul>
<h3 id="事务隔离级别"><a href="#事务隔离级别" class="headerlink" title="事务隔离级别"></a>事务隔离级别</h3><ul>
<li>读未提交：该级别的事务会读取到其它未提交的事务的数据，脏读</li>
<li>读已提交：一个事务可以读取另一个已提交的事务，多次读取会造成不一样的结果，出现不可重复读的问题，Oracle默认隔离级别</li>
<li>可重复读：Mysql默认的级别，同一个事务中，select的结果事务开始时时间点的状态。统一的select操作读到的结果会是一致的，出现幻读问题。</li>
<li>序列化：串行顺序执行，避免了脏读、不可重复读、幻读</li>
</ul>
<h3 id="事务的传播行为"><a href="#事务的传播行为" class="headerlink" title="事务的传播行为"></a>事务的传播行为</h3><ul>
<li>REQUIRED（常用）：如果没有事务，就创建一个新事务，如果当前存在事务，就加入该事务</li>
<li>REQUIRED：无论带你过去存不存在事务，都创建新事务</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span>  <span class="comment">// a事务的所有设置传播到和它共用一个事务的方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">a</span><span class="params">()</span>&#123;</span><br><span class="line">        b();  <span class="comment">// b方法中也有一个事务，REQUIRED表示加入a的事务中。</span></span><br><span class="line">        c();  <span class="comment">// c方法中也有一个事务，REQUIRES_NEW，不加入当前事务，而是新建一个事务</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.REQUIRED)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">b</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Transactional(propagation = Propagation.REQUIRES_NEW)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">c</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h3 id="本地事务的坑"><a href="#本地事务的坑" class="headerlink" title="本地事务的坑"></a>本地事务的坑</h3><p><strong>本类方法互调，默认失效</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">a</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 调用当前类内的方法，b和c方法的事务不会生效，因为绕过了代理对象，相当于直接b和c方法的代码复制过来</span></span><br><span class="line">        b();</span><br><span class="line">        c();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>解决方法</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;!--引入aspectJ--&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-aop&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li><p>@EnableAspectJAutoProxy(exposeProxy&#x3D;true)，开启了aspectJ动态代理功能，对外暴露代理对象</p>
</li>
<li><p>本类互调用代理对象</p>
</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Transactional</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">a</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 获取当前类的代理对象</span></span><br><span class="line">        <span class="type">Apple</span> <span class="variable">appleProxy</span> <span class="operator">=</span> (Apple) AopContext.currentProxy();</span><br><span class="line">        appleProxy.b();</span><br><span class="line">        appleProxy.c();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h2 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h2><p>创建订单包含了 远程锁库存（下订单后，商品锁住）+远程扣积分，<strong>如果远程扣积分出现异常，那么已经执行成功的远程锁库存不会回滚异常</strong>，订单会回滚。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231112210047815.png" alt="image-20231112210047815"></p>
<p><span style="color:red;font-weight:bold">@Transactional是本地事务，在分布式系统，只能控制住自己的回滚，控制不了其它服务的回滚</span></p>
<ol>
<li>远程服务假失败：远程服务其实成功了，由于网络原因没有返回，导致订单回滚，库存却扣减</li>
<li>远程服务执行完成，下面的其它方法出现问题，导致已执行的远程请求，不能回滚</li>
</ol>
<h3 id="CAP定理"><a href="#CAP定理" class="headerlink" title="CAP定理"></a>CAP定理</h3><ul>
<li>一致性：分布式系统，同一时刻有同样的值，<strong>一致性算法raft</strong>。</li>
<li>可用性：集群中一部分节点故障，整体是否还能响应客户请求</li>
<li>分区容错性：区间通信可能失败，部署在不同节点，通信失败</li>
</ul>
<p><strong>一致性和可用性无法兼顾</strong></p>
<h3 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h3><p>对CAP理论的延伸，即使无法做到强一致，可以采用弱一致性</p>
<ul>
<li>基本可用：出现故障，允许损失部分可用性（响应时间，功能上的损失）</li>
<li>软状态：</li>
<li>最终一致性：系统中数据经过一定时间后，达到最终一致的状态。</li>
</ul>
<h3 id="分布式事务解决方案"><a href="#分布式事务解决方案" class="headerlink" title="分布式事务解决方案"></a>分布式事务解决方案</h3><ul>
<li><p>2PC模式</p>
</li>
<li><p>柔性事务-最大努力通知型方案（<strong>高并发</strong>）</p>
</li>
<li><p>柔性事务-可靠消息+最终一致性方案（<strong>高并发</strong>）</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231120135552838.png" alt="image-20231120135552838"></p>
</li>
</ul>
<h3 id="Seata（2PC模式）"><a href="#Seata（2PC模式）" class="headerlink" title="Seata（2PC模式）"></a>Seata（2PC模式）</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/solution-1bdadb80e54074aa3088372c17f0244b.png" alt="img"></p>
<p><strong>只需要一个@GlobalTransactional注解</strong></p>
<ol>
<li>每个服务都需要一个UNDO_LOG表</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">-- 注意此处<span class="number">0.3</span><span class="number">.0</span>+ 增加唯一索引 ux_undo_log</span><br><span class="line">CREATE TABLE `undo_log` (</span><br><span class="line">  `id` bigint(<span class="number">20</span>) NOT NULL AUTO_INCREMENT,</span><br><span class="line">  `branch_id` bigint(<span class="number">20</span>) NOT NULL,</span><br><span class="line">  `xid` varchar(<span class="number">100</span>) NOT NULL,</span><br><span class="line">  `context` varchar(<span class="number">128</span>) NOT NULL,</span><br><span class="line">  `rollback_info` longblob NOT NULL,</span><br><span class="line">  `log_status` <span class="type">int</span>(<span class="number">11</span>) NOT NULL,</span><br><span class="line">  `log_created` datetime NOT NULL,</span><br><span class="line">  `log_modified` datetime NOT NULL,</span><br><span class="line">  `ext` varchar(<span class="number">100</span>) DEFAULT NULL,</span><br><span class="line">  PRIMARY <span class="title function_">KEY</span> <span class="params">(`id`)</span>,</span><br><span class="line">  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)</span><br><span class="line">) ENGINE=InnoDB AUTO_INCREMENT=<span class="number">1</span> DEFAULT CHARSET=utf8;</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>安装seata服务器</li>
</ol>
<p><a target="_blank" rel="noopener" href="https://github.com/seata/seata/releases">https://github.com/seata/seata/releases</a></p>
<ol start="3">
<li>导入依赖</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">	&lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;</span><br><span class="line">	&lt;artifactId&gt;spring-cloud-alibaba-seata&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<ol start="4">
<li><p>a安装seata服务器，主要要和前面依赖对应的seata-all版本对应</p>
</li>
<li><p>registry.conf修改，指定注册中心，及注册中心地址</p>
</li>
<li><p>先启动Nacos，启动seata服务器</p>
</li>
<li><p>使用Seata数据源代理自己的数据源</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 配置代理数据源</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> DataSource <span class="title function_">dataSource</span><span class="params">(DataSourceProperties dataSourceProperties)</span>&#123;</span><br><span class="line">        <span class="type">HikariDataSource</span> <span class="variable">dataSource</span> <span class="operator">=</span> dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.hasText(dataSourceProperties.getName()))&#123;</span><br><span class="line">            dataSource.setPoolName(dataSourceProperties.getName());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DataSourceProxy</span>(dataSource);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>每个服务都必须导入file.conf,和register.conf， 在Seata压缩包中有</p>
<p>file.conf中的vgroup_mapping.my_test_tx_group修改为vgroup_mapping.项目名-fescar-service-group</p>
</li>
</ol>
<p>​			也可以在applicational.yml配置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cloud:</span><br><span class="line">  alibaba:</span><br><span class="line">    seata:</span><br><span class="line">      tx-service-group: 修改后缀，和file.conf一致</span><br></pre></td></tr></table></figure>

<ol start="9">
<li>启动事务</li>
<li>@GlobalTransactional，远程调用的服务不用添加Global事务，每一个远程事务用@Transactional就行</li>
</ol>
<h2 id="压力测试"><a href="#压力测试" class="headerlink" title="压力测试"></a>压力测试</h2><p>当前系统所能承受的最大负荷，找出系统瓶颈。</p>
<p>压力测试应用以下关键条件：<strong>重复、并发、量级、随机变化</strong>。</p>
<blockquote>
<p>性能指标</p>
</blockquote>
<ul>
<li>响应时间：RT，客户端请求到客户端接收到响应的时间。</li>
<li>HPS：每秒点击次数</li>
<li>TPS：系统每秒处理的交易数</li>
<li>QPS：系统每秒处理查询次数</li>
<li>最大响应时间：用户发出请求或指令到系统作出反应的最大时间</li>
<li>90%响应时间，所有用户的响应时间排序，第90%的响应时间</li>
</ul>
<p>主要关注三个指标：</p>
<ol>
<li>吞吐量</li>
<li>响应时间</li>
<li>错误率</li>
</ol>
<blockquote>
<p><strong>Jmeter</strong></p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103151519200.png" alt="image-20231103151519200"></p>
<p>Ramp-Up在1秒内执行200个线程组</p>
<p>循环次数: 每个线程发送100个请求</p>
<h3 id="Address-Already-in-use问题解决"><a href="#Address-Already-in-use问题解决" class="headerlink" title="Address Already in use问题解决"></a>Address Already in use问题解决</h3><p>Window本身提供的端口访问机制的问题，Windows 提供给TCP&#x2F;IP链接的端口为1024-5000，并且要四分钟来循环回收他们。就导致我们在短时间内跑大量的请求时将端口占满了。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103153853366.png" alt="image-20231103153853366"></p>
<h3 id="堆内存和垃圾回收"><a href="#堆内存和垃圾回收" class="headerlink" title="堆内存和垃圾回收"></a>堆内存和垃圾回收</h3><p><strong>影响性能考虑点包括：</strong></p>
<p>首先考虑应用时CPU密集型（计算量大）或IO密集型（数据交换量大）</p>
<p>数据库、应用程序、中间件（Tomcat、Nginx），网络和操作系统等方面。</p>
<h4 id="性能监控"><a href="#性能监控" class="headerlink" title="性能监控"></a>性能监控</h4><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103155633001.png" alt="image-20231103155633001"></p>
<blockquote>
<p><strong>堆</strong></p>
</blockquote>
<p>所有<strong>对象实例及数组都要堆上分配</strong>。堆时垃圾收集器管理的主要区域。也称为GC。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103160303902.png" alt="image-20231103160303902"></p>
<ul>
<li><p>新生代</p>
<p>Eden空间</p>
<p>From Surivor空间</p>
<p>To Survior空间</p>
</li>
<li><p>老年代</p>
</li>
<li><p>永久代（元空间）</p>
</li>
</ul>
<blockquote>
<p><strong>jvisualvm堆监控</strong></p>
</blockquote>
<p>直接在IDEA中按照visualvm插件</p>
<p>安装<a target="_blank" rel="noopener" href="https://github.com/oracle/visualvm/releases/download/2.1.7/visualvm_217.zip"><strong>VisualVM 2.1.7</strong></a>，安装GC插件</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103174644888.png" alt="image-20231103174644888"></p>
<p>休眠：sleep</p>
<p>驻留：线程池里面的空闲线程</p>
<p>监视：阻塞的线程，正在等待锁</p>
<h3 id="中间件对性能的影响"><a href="#中间件对性能的影响" class="headerlink" title="中间件对性能的影响"></a>中间件对性能的影响</h3><p>请求—》Nginx—》gateway网关—》商品服务</p>
<h3 id="简单的优化吞吐量测试"><a href="#简单的优化吞吐量测试" class="headerlink" title="简单的优化吞吐量测试"></a>简单的优化吞吐量测试</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103184805351.png" alt="image-20231103184805351"></p>
<p>首页一级菜单慢的原因：数据库查询和thymeleaf渲染</p>
<p>三级分类数据获取慢的原因：数据库查询</p>
<p>页面的全量数据（Jmeter中加载HTML内容）：</p>
<p><strong>需要优化的内容：</strong></p>
<ul>
<li>数据库查询速度（添加索引）</li>
<li>thymeleaf页面的渲染速度（可以开启缓存）</li>
<li>静态资源</li>
</ul>
<blockquote>
<p>Nginx动静分离</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103184930781.png" alt="image-20231103184930781"></p>
<p><strong>如果一个请求带10个静态请求，非常影响效率</strong>。</p>
<p>原来的静态资源（js、css，图片）在Tomcat中，将静态资源放在Nginx中，提高请求响应速度。</p>
<ul>
<li>规则：&#x2F;static&#x2F;**所有静态请求都由Nginx直接返回，不再去Tomcat中去请求。</li>
</ul>
<blockquote>
<p><strong>gulimall.conf</strong></p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103191443966.png" alt="image-20231103191443966"></p>
<p>gulimall.conf所有的static请求被拦截到  &#x2F;usr&#x2F;share&#x2F;nginx&#x2F;html文件夹下。</p>
<p>其它请求则去gulimall.com</p>
<blockquote>
<p>调整GC垃圾回收</p>
</blockquote>
<p>IDEA编辑配置中设置VM OPTIONs ,分别设置新生代，老年代，元空间等</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Xmx1024m -Xms1024m -Xmn512m</span><br></pre></td></tr></table></figure>

<h3 id="优化三级分类数据"><a href="#优化三级分类数据" class="headerlink" title="优化三级分类数据"></a>优化三级分类数据</h3><p><span style="color:red">优化业务逻辑，避免嵌套查询数据库，将三级分类查询数据库变为一次。</span></p>
<p><strong>getParentCid</strong>将<strong>查询数据的语句</strong>抽象出为一个<strong>方法</strong>，直接从已经查询的Category列表list 数据查询，<strong>不再去查询数据库</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List&lt;CategoryEntity&gt; list = list();</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJson</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 1. 将数据库的多层嵌套查询，变为一次</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// category表的所有数据,只在此查询一次数据库</span></span><br><span class="line">    List&lt;CategoryEntity&gt; list = list();</span><br><span class="line"></span><br><span class="line">    Map&lt;String,List&lt;Category2Vo&gt;&gt; map=<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="comment">// 获取所有的一级分类列表</span></span><br><span class="line">    List&lt;CategoryEntity&gt; level1Category = getParentCid(list,<span class="number">0L</span>);</span><br><span class="line">    level1Category.forEach(level1-&gt;&#123;</span><br><span class="line">        <span class="comment">//根据一级分类获取二级分类</span></span><br><span class="line">        List&lt;CategoryEntity&gt; level2 = getParentCid(list,level1.getCatId());</span><br><span class="line">        List&lt;Category2Vo&gt; level2List=<span class="literal">null</span>;</span><br><span class="line">        <span class="comment">//当前二级分类要存在，才继续查询</span></span><br><span class="line">        <span class="keyword">if</span> (level2!=<span class="literal">null</span>)&#123;</span><br><span class="line">            level2List = level2.stream().map(level2Category -&gt; &#123;</span><br><span class="line">                <span class="comment">// 当前二级分类信息</span></span><br><span class="line">                <span class="type">CategoryEntity</span> <span class="variable">byId</span> <span class="operator">=</span> getById(level2Category);</span><br><span class="line">                <span class="comment">// 当前二级分类对应的三级分类信息</span></span><br><span class="line">                List&lt;CategoryEntity&gt; level3CategoryList = getParentCid(list,byId.getCatId());</span><br><span class="line">                List&lt;Category2Vo.Category3Vo&gt; level3List =<span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">if</span> (level3CategoryList!=<span class="literal">null</span>)&#123;</span><br><span class="line">                    level3List = level3CategoryList.stream().map(level3 -&gt; &#123;</span><br><span class="line">                        <span class="type">CategoryEntity</span> <span class="variable">byId1</span> <span class="operator">=</span> getById(level3);</span><br><span class="line">                        Category2Vo.<span class="type">Category3Vo</span> <span class="variable">category3Vo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Category2Vo</span>.Category3Vo(</span><br><span class="line">                                byId1.getParentCid().toString(),</span><br><span class="line">                                byId1.getCatId().toString(),</span><br><span class="line">                                byId1.getName()</span><br><span class="line">                        );</span><br><span class="line">                        <span class="keyword">return</span> category3Vo;</span><br><span class="line">                    &#125;).collect(Collectors.toList());</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="type">Category2Vo</span> <span class="variable">category2Vo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Category2Vo</span>(</span><br><span class="line">                        String.valueOf(level1.getCatId()),</span><br><span class="line">                        level3List,</span><br><span class="line">                        byId.getCatId().toString(),</span><br><span class="line">                        byId.getName()</span><br><span class="line">                        );</span><br><span class="line">                <span class="keyword">return</span> category2Vo;</span><br><span class="line">            &#125;).collect(Collectors.toList());</span><br><span class="line">        &#125;</span><br><span class="line">       map.put(String.valueOf(level1.getCatId()),level2List);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> map;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> List&lt;CategoryEntity&gt; <span class="title function_">getParentCid</span><span class="params">(List&lt;CategoryEntity&gt; list,</span></span><br><span class="line"><span class="params">                                          Long parent_cid)</span> &#123;</span><br><span class="line">    List&lt;CategoryEntity&gt; collect = list.</span><br><span class="line">            stream().</span><br><span class="line">            filter(item -&gt; Objects.equals(item.getParentCid(), parent_cid)).</span><br><span class="line">            collect(Collectors.toList());</span><br><span class="line">    <span class="keyword">return</span> collect;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="缓存和分布式锁"><a href="#缓存和分布式锁" class="headerlink" title="缓存和分布式锁"></a>缓存和分布式锁</h2><h3 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h3><p>将部分数据放入到缓存中，加锁访问，而DB承担数据库持久化。</p>
<blockquote>
<p>哪些数据适合放到缓存中？</p>
</blockquote>
<ol>
<li>即时性、数据一致性要求不高。</li>
<li>访问量大且更新频率不高的数据（读多写少）</li>
</ol>
<p>如：商品分类、商品列表等适合缓存并加一个失效时间。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103202327572.png" alt="image-20231103202327572"></p>
<p><strong>本地缓存</strong>不能满足分布式服务的要求。<strong>不同服务器中本地缓存内容不统一</strong>。</p>
<p>应该采用分布式缓存。</p>
<blockquote>
<p><strong>分布式缓存</strong></p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231103203716697.png" alt="image-20231103203716697"></p>
<h3 id="整合Redis"><a href="#整合Redis" class="headerlink" title="整合Redis"></a>整合Redis</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&lt;dependency&gt;</span></span><br><span class="line">    <span class="string">&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span></span><br><span class="line">    <span class="string">&lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;</span></span><br><span class="line"><span class="string">&lt;/dependency&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">spring</span><br><span class="line">	redis:</span><br><span class="line">  		host: 192.168.253.128</span><br><span class="line">  		port: 6379</span><br></pre></td></tr></table></figure>

<p>使用<span style="color:red"><strong>StringRedisTemplate操作Redis</strong></span></p>
<h3 id="使用缓存优化-三级分类数据查询"><a href="#使用缓存优化-三级分类数据查询" class="headerlink" title="使用缓存优化-三级分类数据查询"></a>使用缓存优化-三级分类数据查询</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line">StringRedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//opsForValue表示v的值为String</span></span><br><span class="line">ValueOperations&lt;String, String&gt; ops = redisTemplate.opsForValue();</span><br><span class="line">ops.set(<span class="string">&quot;hello&quot;</span>,<span class="string">&quot;你好，黄宏涛&quot;</span>+ UUID.randomUUID().toString());</span><br><span class="line"></span><br><span class="line">System.out.println(ops.get(<span class="string">&quot;hello&quot;</span>));</span><br><span class="line"><span class="comment">//表示k-v中，v的类型为list</span></span><br><span class="line"><span class="comment">//redisTemplate.opsForList();</span></span><br><span class="line"><span class="keyword">return</span> category2VoList;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>方法</p>
</blockquote>
<p><strong>将原来的三级分类查询改为getCatelogJsonDb()方法，并且去掉@Override</strong></p>
<ul>
<li><p>将数据转为JSON字符串： JSON.toJSONString(catelogJsonDb))</p>
</li>
<li><p>将JSON字符串转为对象： </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//TypeReference用于复杂类型的转换，且因为该类是受保护的，采用匿名内部类实现</span></span><br><span class="line">JSON.parseObject(catalogJson, <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;Map&lt;String,List&lt;Category2Vo&gt;&gt;&gt;()&#123;&#125;);</span><br></pre></td></tr></table></figure></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJson</span><span class="params">()</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//加入缓存逻辑,给缓存加入JSON字符串，拿出JSON字符串，逆转为能用的对象</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">catalogJson</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;catalogJSON&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isEmpty(catalogJson))&#123;</span><br><span class="line">        <span class="comment">//缓存中没有，从数据库查询</span></span><br><span class="line">        Map&lt;String, List&lt;Category2Vo&gt;&gt; catelogJsonDb = getCatelogJsonDb();</span><br><span class="line">        <span class="comment">//将查到的数据放到缓存，将查出的对象转JSON放到缓存中，缓存中存的数据推荐用JSON</span></span><br><span class="line">        redisTemplate.opsForValue().set(<span class="string">&quot;catalogJSON&quot;</span>, JSON.toJSONString(catelogJsonDb));</span><br><span class="line">        <span class="keyword">return</span> catelogJsonDb;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//TypeReference用于复杂类型的转换，且因为该类是受保护的，采用匿名内部类实现</span></span><br><span class="line">    Map&lt;String,List&lt;Category2Vo&gt;&gt; result=JSON.parseObject(catalogJson,</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;Map&lt;String,List&lt;Category2Vo&gt;&gt;&gt;()&#123;&#125;);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="压力测试内存溢出（版本已经修复这个问题）"><a href="#压力测试内存溢出（版本已经修复这个问题）" class="headerlink" title="压力测试内存溢出（版本已经修复这个问题）"></a>压力测试内存溢出（版本已经修复这个问题）</h3><p><strong>OutOfDirectMemoryError堆外内存溢出</strong></p>
<h3 id="缓存击穿、穿透"><a href="#缓存击穿、穿透" class="headerlink" title="缓存击穿、穿透"></a>缓存击穿、穿透</h3><ul>
<li><p><strong>缓存穿透：</strong>查询一个不存在的数据，缓存不命中，去查数据库，数据库也没有，返回null，但是我们没有把null放入到缓存，导致这个不存在的数据每次请求去数据库查询，失去了缓存的意义。</p>
<p><strong>解决方案</strong>：null结果缓存，并加入短暂的过期时间</p>
</li>
<li><p><strong>缓存雪崩：</strong>缓存中的数据设置了相同的过期时间，导致缓存在某一时刻同时失效，全部请求转发到数据库，数据库压力过大。</p>
<p>解放方案：在原来的失效时间上加一个随机值</p>
</li>
<li><p><strong>缓存击穿：</strong>某<strong>一个key是非常热点的数据</strong>，如果这个key在大量请求同时进来正好失效**。</p>
<p>解决方案：<strong>加锁</strong>，大量并发只让一个去查，其它人等待，查到后其它人获取到锁，先查缓存，就会有数据，不用去查数据库。</p>
</li>
</ul>
<h3 id="使用加锁解决缓存击穿"><a href="#使用加锁解决缓存击穿" class="headerlink" title="使用加锁解决缓存击穿"></a>使用加锁解决缓存击穿</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104105251715.png" alt="image-20231104105251715"></p>
<p><strong>本地锁</strong></p>
<p>分布式的情况下，每个this都代表不同的对象，锁的是不同得到对象。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只要是同一把锁，就能锁住需要这个锁的所有线程</span></span><br><span class="line"><span class="comment">// synchronized (this)；this代表锁的是当前对象，SpringBoot所有组件在容器中都是单例的。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">synchronized</span> (<span class="built_in">this</span>)&#123;</span><br><span class="line">    <span class="comment">// 拿到锁后，先查缓存有没有，再去查数据库</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">catalogJson</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;catalogJSON&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (!StringUtils.isEmpty(catalogJson)) &#123;</span><br><span class="line">        <span class="comment">// 缓存中有直接返回</span></span><br><span class="line">        Map&lt;String,List&lt;Category2Vo&gt;&gt; result=JSON.parseObject(catalogJson,</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;Map&lt;String,List&lt;Category2Vo&gt;&gt;&gt;()&#123;&#125;);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println(<span class="string">&quot;查询一次数据库&quot;</span>);</span><br><span class="line">    <span class="comment">// 查询数据的业务逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104113206725.png"></p>
<blockquote>
<p>结果却查了两次数据库</p>
</blockquote>
<p>线程1在查完数据库后到把结果放入缓存之间的时间，<strong>线程2去确认缓存没有，又查询了一次，不是原子操作</strong>。</p>
<p>现在把<strong>存入缓存的操作</strong>放到到getCatelogJsonDb方法里面，因为<strong>该方法是原子操作</strong>，所以<strong>不会查询两次数据库</strong>。</p>
<p>解决：<strong>把结果放入缓存的操作应该在原子操作内（同步块），直接存入缓存</strong>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJson</span><span class="params">()</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//加入缓存逻辑,给缓存加入JSON字符串，拿出JSON字符串，逆转为能用的对象</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">catalogJson</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;catalogJSON&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (StringUtils.isEmpty(catalogJson))&#123;</span><br><span class="line">        <span class="comment">//缓存中没有，从数据库查询,应该在getCatelogJsonDb方法里面直接把结果存入缓存</span></span><br><span class="line">        Map&lt;String, List&lt;Category2Vo&gt;&gt; catelogJsonDb = getCatelogJsonDb();</span><br><span class="line">        <span class="keyword">return</span> catelogJsonDb;</span><br><span class="line">    &#125;</span><br><span class="line">        <span class="comment">// 缓存中有直接返回</span></span><br><span class="line">        Map&lt;String, List&lt;Category2Vo&gt;&gt; result = JSON.parseObject(catalogJson,</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;Map&lt;String, List&lt;Category2Vo&gt;&gt;&gt;() &#123;</span><br><span class="line">                &#125;);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span> (<span class="built_in">this</span>)&#123;</span><br><span class="line">    <span class="comment">// 拿到锁后，先查缓存有没有，再去查数据库</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">catalogJson</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;catalogJSON&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (!StringUtils.isEmpty(catalogJson)) &#123;</span><br><span class="line">        <span class="comment">// 缓存中有直接返回</span></span><br><span class="line">        Map&lt;String,List&lt;Category2Vo&gt;&gt; result=JSON.parseObject(catalogJson,</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">TypeReference</span>&lt;Map&lt;String,List&lt;Category2Vo&gt;&gt;&gt;()&#123;&#125;);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println(<span class="string">&quot;查询一次数据库&quot;</span>);</span><br><span class="line">    <span class="comment">// 查询数据的业务逻辑</span></span><br><span class="line">    ....</span><br><span class="line">    ....</span><br><span class="line">    <span class="comment">//将查到的数据放到缓存，将查出的对象转JSON放到缓存中，缓存中存的数据推荐用JSON</span></span><br><span class="line">    redisTemplate.opsForValue().set(<span class="string">&quot;catalogJSON&quot;</span>, JSON.toJSONString(map));</span><br><span class="line">    <span class="keyword">return</span> map;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="本地锁在分布式下的表现"><a href="#本地锁在分布式下的表现" class="headerlink" title="本地锁在分布式下的表现"></a>本地锁在分布式下的表现</h3><p><strong>本地锁只能锁当前进程，所以我们需要分布式锁。</strong></p>
<p><span style="color:red"><strong>同时启动三个gulimall-product服务，每个服务使用本地锁都查了数据库</strong>。</span></p>
<h3 id="分布式锁原理"><a href="#分布式锁原理" class="headerlink" title="分布式锁原理"></a>分布式锁原理</h3><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104182219196.png" alt="image-20231104182219196"></p>
<p>所有的服务去一个共同的占坑</p>
<p> <img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104190134013.png" alt="image-20231104190134013"></p>
<p><strong>死锁问题：没有成功执行删锁操作：</strong></p>
<ul>
<li>设置锁的过期时间，自动删除</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJsonDbWithRedisLock</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 占用分布式锁，去Redis占坑,setIfAbsent就是setnx，不存在就设置</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">lock</span> <span class="operator">=</span> redisTemplate.opsForValue().setIfAbsent(<span class="string">&quot;lock&quot;</span>, <span class="string">&quot;111&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (lock)&#123;</span><br><span class="line">        <span class="comment">// 加锁成功</span></span><br><span class="line">        <span class="comment">// 设置过期时间,30秒后自动删除</span></span><br><span class="line">        redisTemplate.expire(<span class="string">&quot;lock&quot;</span>,<span class="number">40</span>,TimeUnit.SECONDS);</span><br><span class="line">        Map&lt;String, List&lt;Category2Vo&gt;&gt; dataFromDb = getDataFromDb();</span><br><span class="line">        <span class="comment">// 删除锁</span></span><br><span class="line">        redisTemplate.delete(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> dataFromDb;</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 加锁失败...重试</span></span><br><span class="line">        <span class="keyword">return</span> getCatelogJsonDbWithRedisLock(); <span class="comment">// 自旋的方式</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>但如果if(lock)执行完毕，突然断电，锁会没有过期时间。</p>
<p><strong>占坑和设置过期时间采用原子操作可以避免这个问题</strong>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Boolean</span> <span class="variable">lock</span> <span class="operator">=</span> redisTemplate.opsForValue().setIfAbsent(<span class="string">&quot;lock&quot;</span>, <span class="string">&quot;111&quot;</span>,<span class="number">300</span>,TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure>



<p>假如业务执行时间过长，锁已经删除（因为过期）此时删锁有可能删除的是别人的锁。</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104192318104.png" alt="image-20231104192318104"></p>
<p>删除锁值之前，先判断是否是自己的锁。但也有问题。不是原子操作，如果程序执行完if (uuid.equals(lockValue)){刚好过期，此时比人会给Lock设一个值，会删除这个。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">lockValue</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;lcok&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (uuid.equals(lockValue))&#123;</span><br><span class="line">    <span class="comment">// 确保删除的是自己的锁</span></span><br><span class="line">    redisTemplate.delete(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>获取值对比和删锁必须是原子操作，采用Redis提供的脚本解锁</strong>。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">script</span> <span class="operator">=</span> <span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then return redis.call(&#x27;del&#x27;, KEYS[1]) else return 0 end&quot;</span>;</span><br><span class="line">redisTemplate.execute(<span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;Integer&gt;(script,Integer.class),Arrays.asList(<span class="string">&quot;lock&quot;</span>),uuid);</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104194105470.png" alt="image-20231104194105470"></p>
<p>最终实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Map&lt;String,List&lt;Category2Vo&gt;&gt; <span class="title function_">getCatelogJsonDbWithRedisLock</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="comment">// 占用分布式锁，去Redis占坑,setIfAbsent就是setnx，不存在就设置</span></span><br><span class="line">    <span class="comment">// 设置过期时间,30秒后自动删除</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">uuid</span> <span class="operator">=</span> UUID.randomUUID().toString();</span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">lock</span> <span class="operator">=</span> redisTemplate.opsForValue().setIfAbsent(<span class="string">&quot;lock&quot;</span>, uuid,</span><br><span class="line">            <span class="number">300</span>,TimeUnit.SECONDS);</span><br><span class="line">    Map&lt;String, List&lt;Category2Vo&gt;&gt; dataFromDb;</span><br><span class="line">    <span class="keyword">if</span> (lock)&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;获取分布式锁成功&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            dataFromDb = getDataFromDb();</span><br><span class="line">        &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 采用Redis提供的脚本删锁，原子删锁</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">script</span> <span class="operator">=</span> <span class="string">&quot;if redis.call(&#x27;get&#x27;, KEYS[1]) == ARGV[1] then return redis.call(&#x27;del&#x27;, KEYS[1]) else return 0 end&quot;</span>;</span><br><span class="line">            redisTemplate.execute(<span class="keyword">new</span> <span class="title class_">DefaultRedisScript</span>&lt;Long&gt;(script,Long.class),Arrays.asList(<span class="string">&quot;lock&quot;</span>),uuid);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> dataFromDb;</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;获取分布式锁失败-----等待200ms重试&quot;</span>);</span><br><span class="line">        Thread.sleep(<span class="number">200</span>);</span><br><span class="line">        <span class="comment">// 加锁失败...重试</span></span><br><span class="line">        <span class="keyword">return</span> getCatelogJsonDbWithRedisLock(); <span class="comment">// 自旋的方式</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Redisson简介-Redis分布式锁"><a href="#Redisson简介-Redis分布式锁" class="headerlink" title="Redisson简介-Redis分布式锁"></a>Redisson简介-Redis分布式锁</h3><ol>
<li>导入依赖</li>
</ol>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&lt;!--使用redisson用于分布式锁--&gt;</span></span><br><span class="line"><span class="string">&lt;dependency&gt;</span></span><br><span class="line">    <span class="string">&lt;groupId&gt;org.redisson&lt;/groupId&gt;</span></span><br><span class="line">    <span class="string">&lt;artifactId&gt;redisson&lt;/artifactId&gt;</span></span><br><span class="line">    <span class="string">&lt;version&gt;3.12.0&lt;/version&gt;</span></span><br><span class="line"><span class="string">&lt;/dependency&gt;</span></span><br></pre></td></tr></table></figure>

<ol start="2">
<li>配置方法一——程序化配置</li>
</ol>
<h4 id="Lock锁测试"><a href="#Lock锁测试" class="headerlink" title="Lock锁测试"></a>Lock锁测试</h4><blockquote>
<p>可重入锁Reentrant Lock</p>
</blockquote>
<p>A里面调用了B，A和B使用同一把锁。B看A已经加了这把锁，B就直接执行。</p>
<ul>
<li>Redisson 阻塞式等待，<strong>默认加锁是30s,会自动关闭锁，即使没有执行解锁操作，也会自动解锁</strong>.  锁的自动续期，如果业务超长，运行期间会给锁续上30s</li>
<li>三分之一的看门狗时间（30s），即为10s后自动续期。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">//获取一把锁，只要是锁的名字一样，就是同一把锁</span></span><br><span class="line">    <span class="type">RLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getLock(<span class="string">&quot;my-lock&quot;</span>);</span><br><span class="line">    <span class="comment">// 加锁</span></span><br><span class="line">    lock.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;加锁成功，当前线程&quot;</span>+Thread.currentThread().getId());</span><br><span class="line">        Thread.sleep (<span class="number">30000</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="comment">// Redisson 阻塞式等待，默认加锁是30s,会自动关闭锁，即使没有执行解锁操作.</span></span><br><span class="line">        <span class="comment">// 锁的自动续期，如果业务超长，运行期间会给锁续上30s</span></span><br><span class="line">        System.out.println(<span class="string">&quot;释放锁&quot;</span>+Thread.currentThread().getId());</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;hello&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>Lock锁看门狗</p>
</blockquote>
<p><span style="color:red">也可以自定义解锁时间，但不会自动续期，且解锁时间必须大于业务执行时间</span></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 加锁</span></span><br><span class="line">lock.lock(<span class="number">10</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure>

<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231104211353802.png" alt="image-20231104211353802"></p>
<blockquote>
<p>推荐使用方法</p>
</blockquote>
<p>默认设置自定义解锁时间</p>
<h4 id="读写锁"><a href="#读写锁" class="headerlink" title="读写锁"></a>读写锁</h4><p>trylock可以设置当到了过期时间，可以延长多少</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//第一个10代表延长时间，第二个10代表过期时间</span></span><br><span class="line">lock.tryLock(<span class="number">10</span>,<span class="number">10</span>,TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure>

<p>公平锁Fair Lock</p>
<p><strong>读写锁：</strong></p>
<p>保证一定能读取到最新的数据，修改期间，写锁是一个排他锁（互斥锁）。读锁是一个共享锁，<strong>写锁没释放，读锁取无法读取</strong>。</p>
<ul>
<li>写—-读： 等待写锁释放</li>
<li>写+写：阻塞方式</li>
<li>读+写：有读锁，写锁也要等待</li>
<li>读+读：相当于无锁</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line">StringRedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line"><span class="meta">@GetMapping(&quot;/write&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">//获取一把锁，只要是锁的名字一样，就是同一把锁</span></span><br><span class="line">    <span class="type">RReadWriteLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getReadWriteLock(<span class="string">&quot;rw-lock&quot;</span>);</span><br><span class="line">    <span class="type">RLock</span> <span class="variable">rLock</span> <span class="operator">=</span> lock.writeLock();</span><br><span class="line">    String s=<span class="string">&quot;&quot;</span>;</span><br><span class="line">       <span class="keyword">try</span>&#123;</span><br><span class="line">           <span class="comment">// 1.改数据加写锁，读数据加读锁</span></span><br><span class="line">           rLock.lock();</span><br><span class="line">           s=UUID.randomUUID().toString();</span><br><span class="line">           Thread.sleep(<span class="number">30000</span>);</span><br><span class="line">           redisTemplate.opsForValue().set(<span class="string">&quot;writeValue&quot;</span>,s);</span><br><span class="line">       &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">       &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">            rLock.unlock();</span><br><span class="line">       &#125;</span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@GetMapping(&quot;/read&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">read</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">//获取一把锁，只要是锁的名字一样，就是同一把锁</span></span><br><span class="line">    <span class="type">RReadWriteLock</span> <span class="variable">lock</span> <span class="operator">=</span> redissonClient.getReadWriteLock(<span class="string">&quot;rw-lock&quot;</span>);</span><br><span class="line">    <span class="comment">// 加读锁</span></span><br><span class="line">    <span class="type">RLock</span> <span class="variable">rLock</span> <span class="operator">=</span> lock.readLock();</span><br><span class="line">    rLock.lock();</span><br><span class="line">    String s=<span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        s= redisTemplate.opsForValue().get(<span class="string">&quot;writeValue&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">    &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">        rLock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="闭锁"><a href="#闭锁" class="headerlink" title="闭锁"></a>闭锁</h4><p>计数，必须等待所有都执行完，才会释放锁</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/go/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="type">RCountDownLatch</span> <span class="variable">door</span> <span class="operator">=</span> redissonClient.getCountDownLatch(<span class="string">&quot;door&quot;</span>);</span><br><span class="line">    door.countDown(); <span class="comment">//计数减一</span></span><br><span class="line">    <span class="keyword">return</span> id+<span class="string">&quot;班的人走完了&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@GetMapping(&quot;/read&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">read</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="type">RCountDownLatch</span> <span class="variable">door</span> <span class="operator">=</span> redissonClient.getCountDownLatch(<span class="string">&quot;door&quot;</span>);</span><br><span class="line">    door.trySetCount(<span class="number">5</span>);</span><br><span class="line">    door.await(); <span class="comment">// 等待闭锁都完成</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;放假了&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="信号量Semaphore"><a href="#信号量Semaphore" class="headerlink" title="信号量Semaphore"></a>信号量Semaphore</h4><p>分布式限流，假如有10000个发请求，可以给每个请求设置信号量，等别人用完</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/park&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="type">RSemaphore</span> <span class="variable">park</span> <span class="operator">=</span> redissonClient.getSemaphore(<span class="string">&quot;park&quot;</span>);</span><br><span class="line">    park.acquire();<span class="comment">// 占一个车位，获取一个信号，一直等待，直到获取成功</span></span><br><span class="line">    <span class="comment">//park.tryAcquire(); 有就用，没有也不等待，返回一个true或false，</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;ok&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@GetMapping(&quot;/go&quot;)</span></span><br><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">read</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">RSemaphore</span> <span class="variable">park</span> <span class="operator">=</span> redissonClient.getSemaphore(<span class="string">&quot;park&quot;</span>);</span><br><span class="line">    park.release();<span class="comment">// 释放一个车位</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;ok&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="缓存一致性（缓存和数据库一致性）解决"><a href="#缓存一致性（缓存和数据库一致性）解决" class="headerlink" title="缓存一致性（缓存和数据库一致性）解决"></a>缓存一致性（缓存和数据库一致性）解决</h4><ul>
<li>双写模式</li>
<li>失效模式</li>
</ul>
<blockquote>
<p>双写模式</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105093817015.png" alt="image-20231105093817015"></p>
<p>双写模式：修改完数据库，写入缓存，可能会产生脏数据。</p>
<p>解决方案：</p>
<ol>
<li>写数据库和写缓存加锁</li>
<li>不管，等缓存失效后，可以得到正确的数据</li>
</ol>
<blockquote>
<p>失效模式</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105093849458.png" alt="image-20231105093849458"></p>
<p>失效模式：修改完数据库，删除缓存</p>
<p>解决方案：</p>
<ol>
<li>经常修改的数据，直接读数据库，不用缓存。</li>
</ol>
<blockquote>
<p>数据一致性解决方案</p>
</blockquote>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105095139982.png" alt="image-20231105095139982"></p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105100816859.png" alt="image-20231105100816859"></p>
<p>每个人的首页不一样，可以用canal实现</p>
<h3 id="Spring-Cache"><a href="#Spring-Cache" class="headerlink" title="Spring Cache"></a>Spring Cache</h3><h4 id="简介-1"><a href="#简介-1" class="headerlink" title="简介"></a>简介</h4><p>SpringCache统一不同的缓存技术</p>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231105101647640.png" alt="image-20231105101647640"></p>
<h4 id="整合Spring-Cache"><a href="#整合Spring-Cache" class="headerlink" title="整合Spring-Cache"></a>整合Spring-Cache</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">    &lt;!--引入Redis缓存--&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>配置</p>
</blockquote>
<p>自动配置了缓存管理器RedisCacheManager</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">spring</span></span><br><span class="line">	<span class="attr">cache:</span></span><br><span class="line">    	<span class="attr">type:</span> <span class="string">redis</span></span><br><span class="line">    	<span class="attr">redis:</span></span><br><span class="line">      		<span class="attr">time-to-live:</span> <span class="number">3600000</span> <span class="comment"># 单位毫秒，缓存过期时间</span></span><br><span class="line">      		<span class="attr">cache-null-values:</span> <span class="literal">true</span> <span class="comment"># 是否缓存null值，可以防止缓存穿透</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>缓存注解</p>
</blockquote>
<ul>
<li>Cacheable:触发将数据保存到缓存的操作</li>
<li>@CacheEvict：触发将数据从缓存删除，<strong>失效模式</strong></li>
<li>@CachePut：不影响方法执行，更新缓存，把本次的结果放到缓存中，<strong>双写模式</strong></li>
<li>@Caching：组合以上缓存操作</li>
<li>@CacheConfig：在类级别共享缓存的相同的配置</li>
</ul>
<p><span style="color:red;font-weight:bold">使用时需要开启@EnableCaching</span></p>
<h4 id="Cacheable注解"><a href="#Cacheable注解" class="headerlink" title="@Cacheable注解"></a>@Cacheable注解</h4><figure class="highlight tcl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">每一个需要缓存的数据我们都要来指定要放到哪个名字（哪个区）的缓存，缓存分区（按照业务类型分）</span><br><span class="line">* @Cacheable(<span class="string">&quot;category&quot;</span>)</span><br><span class="line">   代表当前方法的结果需要缓存，</span><br><span class="line">   如果缓存有，方法不调用，</span><br><span class="line">   没有，最后将方法的结果放入到缓存。</span><br><span class="line">   默认行为：</span><br><span class="line">       <span class="number">1</span>)缓存中有，方法不调用</span><br><span class="line">       <span class="number">2</span>)key默认自动生成，缓存的名字：SimpleKey[] 自动生成key值</span><br><span class="line">       <span class="number">3</span>)缓存的value值，默认是使用jdk序列化机制，将jdk序列化后数据放到Value中</span><br><span class="line">       <span class="number">4</span>)默认ttl时间为<span class="number">-1</span>,永不过期</span><br><span class="line">       <span class="number">5</span>)Value代表缓存分区</span><br><span class="line">   自定义：</span><br><span class="line">       <span class="number">1</span>)指定生成的缓存用的key，key属性指定，接收一个SpEL,</span><br><span class="line">           使用字符串时要加一个单引号: key = <span class="string">&quot;&#x27;level1&#x27;&quot;</span></span><br><span class="line">           使用方法名可以用SpEL表达式：key = <span class="string">&quot;#root.method.name&quot;</span></span><br><span class="line">       <span class="number">2</span>）指定缓存数据的过期时间,在配置文件设置过期时间</span><br><span class="line">       <span class="number">3</span>)将数据保存为JSON格式</span><br></pre></td></tr></table></figure>

<p><span style="color:red;font-weight:bold">@Cahce注解中key使用字符串时要加一个单引号，重点</span></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Cacheable(value = &quot;category&quot;,key = &quot;#root.method.name&quot;)</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> List&lt;CategoryEntity&gt; <span class="title function_">getLevel1Category</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;查询了一级分类&quot;</span>);</span><br><span class="line">    List&lt;CategoryEntity&gt; catLevel = list(<span class="keyword">new</span> <span class="title class_">QueryWrapper</span>&lt;CategoryEntity&gt;().eq(<span class="string">&quot;parent_cid&quot;</span>, <span class="number">0</span>));</span><br><span class="line">    <span class="keyword">return</span> catLevel;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="自定义缓存配置"><a href="#自定义缓存配置" class="headerlink" title="自定义缓存配置"></a>自定义缓存配置</h4><p>CacheAutoConfiguration—-&gt;RedisCacheAutoConfiguration—-&gt;自动配置RedisCacheManager—&gt;初始化所有缓存</p>
<p>—-&gt;如果RedisCacheConfiguration有就用已有的，否则使用默认的配置</p>
<p>—–&gt;想要改缓存配置，只需要给容器中添加一个<strong>RedisCacheConfiguration</strong></p>
<h4 id="JSON序列化"><a href="#JSON序列化" class="headerlink" title="JSON序列化"></a>JSON序列化</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@EnableConfigurationProperties(CacheProperties.class)</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyCacheConfig</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// @Autowired</span></span><br><span class="line">    <span class="comment">// public CacheProperties cacheProperties;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 配置文件的配置没有用上</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisCacheConfiguration <span class="title function_">redisCacheConfiguration</span><span class="params">(CacheProperties cacheProperties)</span> &#123;</span><br><span class="line">        <span class="comment">// 容器中已经有CacheProperties，可以在形参中直接调用</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="type">RedisCacheConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> RedisCacheConfiguration.defaultCacheConfig();</span><br><span class="line">        <span class="comment">// config = config.entryTtl();</span></span><br><span class="line">        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(<span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>()));</span><br><span class="line">        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(<span class="keyword">new</span> <span class="title class_">GenericJackson2JsonRedisSerializer</span>()));</span><br><span class="line"></span><br><span class="line">        CacheProperties.<span class="type">Redis</span> <span class="variable">redisProperties</span> <span class="operator">=</span> cacheProperties.getRedis();</span><br><span class="line">        <span class="comment">//将配置文件中所有的配置都生效</span></span><br><span class="line">        <span class="keyword">if</span> (redisProperties.getTimeToLive() != <span class="literal">null</span>) &#123;</span><br><span class="line">            config = config.entryTtl(redisProperties.getTimeToLive());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (redisProperties.getKeyPrefix() != <span class="literal">null</span>) &#123;</span><br><span class="line">            config = config.prefixKeysWith(redisProperties.getKeyPrefix());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!redisProperties.isCacheNullValues()) &#123;</span><br><span class="line">            config = config.disableCachingNullValues();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!redisProperties.isUseKeyPrefix()) &#123;</span><br><span class="line">            config = config.disableKeyPrefix();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> config;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="CacheEvict"><a href="#CacheEvict" class="headerlink" title="@CacheEvict"></a>@CacheEvict</h4><p>修改完后，从缓存中删除数据，即上面的<strong>缓存失效模式</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 缓存失效模式：更新分类，会删除缓存</span></span><br><span class="line"><span class="meta">@CacheEvict(value = &quot;category&quot;,key =&quot;getLevel1Category&quot; )</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateCascade</span><span class="params">(CategoryEntity category)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.updateById(category);</span><br><span class="line">    categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="Caching"><a href="#Caching" class="headerlink" title="@Caching"></a>@Caching</h4><blockquote>
<p>如果更新分类，需要删除多个不同的缓存</p>
</blockquote>
<ol>
<li>方法一</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Caching(evict = &#123;</span></span><br><span class="line"><span class="meta">        @CacheEvict(value = &quot;category&quot;,key = &quot;&#x27;getLevel1Categorys&#x27;&quot;),</span></span><br><span class="line"><span class="meta">        @CacheEvict(value =  &quot;category&quot;,key = &quot;&#x27;getCatelogJson&#x27;&quot;)</span></span><br><span class="line"><span class="meta">&#125;)</span></span><br><span class="line">同时删除两个不同的缓存</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>方法二</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 删除当前分区下的所有缓存</span></span><br><span class="line"><span class="meta">@CacheEvict(value = &quot;category&quot;,allEntries = true)</span></span><br></pre></td></tr></table></figure>

<h4 id="SpringCache的原理与不足"><a href="#SpringCache的原理与不足" class="headerlink" title="SpringCache的原理与不足"></a>SpringCache的原理与不足</h4><blockquote>
<p><strong>读模式</strong></p>
</blockquote>
<ol>
<li>缓存穿透：查询一个null数据，解决：缓存空数据，ache-null-value&#x3D;true</li>
<li>缓存击穿：大量并发进来访问一个正好过期的数据。解决：加锁，**@Cacheable注解添加sync&#x3D;true，即可加锁**</li>
<li>缓存雪崩：大量key同时过期。解决：加随机时间，加上过期时间</li>
</ol>
<blockquote>
<p><strong>写模式</strong>（缓存与数据库一致）</p>
</blockquote>
<ol>
<li>读写加锁。</li>
<li>引入canal，感知到Mysql的更新去更新数据库</li>
<li>读多写多，直接去数据库查询</li>
</ol>
<blockquote>
<p>总结</p>
</blockquote>
<p>常规数据（<strong>读多写少、即时性、一致性要求不高</strong>）：可以用SpringCache，写模式，只要缓存的数据有过期时间就够了</p>
<p>特殊数据：特殊设计</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[client]</span><br><span class="line"><span class="keyword">default</span>-character-set=utf8</span><br><span class="line">[mysql]</span><br><span class="line"><span class="keyword">default</span>-character-set=utf8</span><br><span class="line">[mysqld]</span><br><span class="line">init_connect=<span class="string">&#x27;SET collation_connection = utf8_unicode_ci&#x27;</span> init_connect=<span class="string">&#x27;SET NAMES utf8&#x27;</span> character-set-server=utf8</span><br><span class="line">collation-server=utf8_unicode_ci</span><br><span class="line">skip-character-set-client-handshake</span><br><span class="line">skip-name-resolve</span><br></pre></td></tr></table></figure>

<h1 id="异步-线程"><a href="#异步-线程" class="headerlink" title="异步-线程"></a>异步-线程</h1><h2 id="初始化线程的四种方式"><a href="#初始化线程的四种方式" class="headerlink" title="初始化线程的四种方式"></a>初始化线程的四种方式</h2><ol>
<li>继承Thread</li>
<li>实现Runnable接口</li>
<li>实现Callable接口</li>
<li>线程池</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException &#123;</span><br><span class="line">    <span class="comment">// 1.继承Thread</span></span><br><span class="line">    <span class="type">Thread01</span> <span class="variable">thread01</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread01</span>();</span><br><span class="line">    thread01.start(); <span class="comment">//启动线程</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2.实现Runnable接口，run方法</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable01</span>()).start();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3.实现Callable接口,call方法可以有返回值</span></span><br><span class="line">    FutureTask&lt;Integer&gt; integerFutureTask = <span class="keyword">new</span> <span class="title class_">FutureTask</span>&lt;&gt;(<span class="keyword">new</span> <span class="title class_">Callable01</span>());</span><br><span class="line">    <span class="comment">// 3.1阻塞等待，线程执行完毕，get可以获取返回值</span></span><br><span class="line">    <span class="type">Integer</span> <span class="variable">i</span> <span class="operator">=</span> integerFutureTask.get();</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Thread</span>(integerFutureTask).start();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 1和2不能得到返回值，3，即Callable可以有返回值</span></span><br><span class="line"><span class="comment">     * 1,2,3不能控制资源，线程池可以控制资源，不会导致资源耗尽</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 4.线程池</span></span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">threadPool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line">    <span class="comment">// execute和submit都是执行方式，但后者有返回值</span></span><br><span class="line">    threadPool.execute(<span class="keyword">new</span> <span class="title class_">Runnable01</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Thread01</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;当前线程&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Runnable01</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><h3 id="使用ThreadPoolExecutor创建线程池"><a href="#使用ThreadPoolExecutor创建线程池" class="headerlink" title="使用ThreadPoolExecutor创建线程池"></a>使用ThreadPoolExecutor创建线程池</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 七大参数</span></span><br><span class="line"><span class="comment"> * corePoolSize - 即使它们处于空闲状态，也要保留在池中的线程数，</span></span><br><span class="line"><span class="comment"> *                  除非allowCoreThreadTimeOut设置为true</span></span><br><span class="line"><span class="comment"> * maximumPoolSize - 允许在池中存在的最大线程数</span></span><br><span class="line"><span class="comment"> * keepAliveTime - 最大存活时间，当线程数大于核心时，空闲线程等待新任务时间，</span></span><br><span class="line"><span class="comment"> *                  释放空闲线程（不包括核心线程）</span></span><br><span class="line"><span class="comment"> * unit 最大存活时间的时间单位</span></span><br><span class="line"><span class="comment"> * BlockingQueue - 阻塞队列，如果任务很多，就会将多的任务放到队列里面，</span></span><br><span class="line"><span class="comment"> *                  只要线程空闲，就去队列里面取出来</span></span><br><span class="line"><span class="comment"> * threadFactory - 线程创建的工厂</span></span><br><span class="line"><span class="comment"> * handler- 如果阻塞队列满了，按照我们指定的拒绝策略拒绝执行任务</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">ThreadPoolExecutor</span> <span class="variable">executor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>();</span><br></pre></td></tr></table></figure>

<ul>
<li>core线程池大小,<strong>是日常用的, 即使没有任务,也会在线程池中存在,阻塞队列没有满</strong></li>
<li>max最大线程池大小,<strong>是阻塞队列满了之后,开启到最大线程池</strong></li>
<li>拒绝策略是: 当阻塞队列和max最大线程池都满了,还有人要进来,怎么拒绝</li>
</ul>
<p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/Untitled.png" alt="Untitled"></p>
<blockquote>
<p>拒绝策略</p>
</blockquote>
<figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">AbortPolicy(默认)     这个拒绝策略是,人满了之后,还有人进来丢弃，抛出异常</span><br><span class="line">CallerRunsPolicy      不丢弃，只要线程池没关，执行Runnable的run方法，同步执行，而不是异步执行</span><br><span class="line">DiscardPolicy()       不会抛出异常,丢掉任务</span><br><span class="line">DiscardOldestPolicy() 不会抛出异常,新的任务进来，如果旧的任务没有执行完，就丢弃掉</span><br></pre></td></tr></table></figure>

<h3 id="使用Executors创建线程池"><a href="#使用Executors创建线程池" class="headerlink" title="使用Executors创建线程池"></a>使用Executors创建线程池</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Executors.newFixedThreadPool(); <span class="comment">//固定线程池大小</span></span><br><span class="line">Executors.newScheduledThreadPool(); <span class="comment">//定时任务的线程池</span></span><br><span class="line">Executors.newCachedThreadPool();  <span class="comment">// 核心线程为0，所有都可回收</span></span><br><span class="line">Executors.newSingleThreadExecutor();<span class="comment">//单线程的线程池，逐个执行</span></span><br></pre></td></tr></table></figure>

<p><img src="C:\Users\ZC\Desktop\Untitled.png" alt="Untitled"></p>
<blockquote>
<p>为什么使用线程池</p>
</blockquote>
<ol>
<li>降低资源消耗：重复利用已经创建好的线程，降低线程创建和销毁带来的损耗</li>
<li>提高响应速度：当线程池中还有空闲线程时，如果有新任务进来，直接就能执行，无需等待</li>
<li>提高线程的可管理性：线程池会根据当前系统的特点对池内线程进行优化处理。</li>
</ol>
<h2 id="异步编排CompletableFuture"><a href="#异步编排CompletableFuture" class="headerlink" title="异步编排CompletableFuture"></a>异步编排CompletableFuture</h2><p><img src="https://gitee.com/htt-sub/pic-go/raw/master/images/image-20231107094456067.png" alt="image-20231107094456067"></p>
<p>若是逐个执行，花费6.5秒，很慢，可以异步执行。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>可以异步执行，互相没有没有关系，但是<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>都需要<span class="number">1</span>的数据</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CompletableFuture</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">Future</span>&lt;T&gt;, CompletionStage&lt;T&gt; &#123;</span><br><span class="line">    可以获取到返回结果，Future代表异步计算的结果</span><br></pre></td></tr></table></figure>

<h3 id="创建异步对象"><a href="#创建异步对象" class="headerlink" title="创建异步对象"></a>创建异步对象</h3><p>CompletableFuture提供了四个<strong>静态方法</strong>创建一个异步操作</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">Supplier是一个函数式接口</span><br><span class="line"></span><br><span class="line"><span class="comment">// 有返回值，Supply代表传入的异步任务</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">supplyAsync</span><span class="params">(Supplier&lt;U&gt; supplier)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> asyncSupplyStage(asyncPool, supplier);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">supplyAsync</span><span class="params">(Supplier&lt;U&gt; supplier,</span></span><br><span class="line"><span class="params">                                                       Executor executor)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> asyncSupplyStage(screenExecutor(executor), supplier);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 没有返回值Void，在默认线程池执行</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> CompletableFuture&lt;Void&gt; <span class="title function_">runAsync</span><span class="params">(Runnable runnable)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> asyncRunStage(asyncPool, runnable);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 没有返回值Void，在指定的线程池执行</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> CompletableFuture&lt;Void&gt; <span class="title function_">runAsync</span><span class="params">(Runnable runnable,</span></span><br><span class="line"><span class="params">                                                   Executor executor)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> asyncRunStage(screenExecutor(executor), runnable);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException &#123;</span><br><span class="line">    <span class="comment">// 线程池，不销毁，程序会一直运行</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 自定义的异步任务,采用自定义的线程池，runAsync无返回值</span></span><br><span class="line">    <span class="comment">/*CompletableFuture.runAsync(()-&gt;&#123;</span></span><br><span class="line"><span class="comment">        System.out.println(&quot;我是黄宏涛&quot;+Thread.currentThread().getId());</span></span><br><span class="line"><span class="comment">    &#125;,executor);*/</span></span><br><span class="line">    <span class="comment">// 有返回值的，采用自定义的线程池，supplyAsync</span></span><br><span class="line">    CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;黄宏涛&quot;</span>,</span><br><span class="line">            executor);</span><br><span class="line">    <span class="comment">// get获取到返回值</span></span><br><span class="line">    System.out.println(future.get());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="计算完成时回调方法"><a href="#计算完成时回调方法" class="headerlink" title="计算完成时回调方法"></a>计算完成时回调方法</h3><p>whenComplete<strong>可以感知方法成功到结果，但不能处理</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 两个参数，第一个代表结果，第二代表异常</span></span><br><span class="line"><span class="keyword">public</span> CompletableFuture&lt;T&gt; <span class="title function_">whenComplete</span><span class="params">(</span></span><br><span class="line"><span class="params">        BiConsumer&lt;? <span class="built_in">super</span> T, ? <span class="built_in">super</span> Throwable&gt; action)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> uniWhenCompleteStage(<span class="literal">null</span>, action);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> CompletableFuture&lt;T&gt; <span class="title function_">whenCompleteAsync</span><span class="params">(</span></span><br><span class="line"><span class="params">        BiConsumer&lt;? <span class="built_in">super</span> T, ? <span class="built_in">super</span> Throwable&gt; action)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> uniWhenCompleteStage(asyncPool, action);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> CompletableFuture&lt;T&gt; <span class="title function_">whenCompleteAsync</span><span class="params">(</span></span><br><span class="line"><span class="params">        BiConsumer&lt;? <span class="built_in">super</span> T, ? <span class="built_in">super</span> Throwable&gt; action, Executor executor)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> uniWhenCompleteStage(screenExecutor(executor), action);</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">public</span> CompletableFuture&lt;T&gt; <span class="title function_">exceptionally</span><span class="params">(</span></span><br><span class="line"><span class="params">        Function&lt;Throwable, ? extends T&gt; fn)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> uniExceptionallyStage(fn);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException &#123;</span><br><span class="line">    <span class="comment">// 有返回值的，采用自定义的线程池，supplyAsync</span></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future = CompletableFuture.supplyAsync(() -&gt;<span class="number">1</span>/<span class="number">0</span>,</span><br><span class="line">            executor).</span><br><span class="line">            <span class="comment">// whenComplete异步任务完成时回调这个方法，获取到结果和异常信息，但无法处理异常(类似于监听器)</span></span><br><span class="line">            whenComplete((result,exception)-&gt;&#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;异步任务完成结果是&quot;</span>+result);</span><br><span class="line">                System.out.println(<span class="string">&quot;出现异常&quot;</span>+exception);&#125;).</span><br><span class="line">            <span class="comment">// exceptionally中t代表接收到的异常信息，同时返回默认值,修改返回结果</span></span><br><span class="line">            exceptionally(t -&gt; &#123;</span><br><span class="line">                System.out.println(t);</span><br><span class="line">                <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">            &#125;);</span><br><span class="line">    System.out.println(future.get());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="handle方法"><a href="#handle方法" class="headerlink" title="handle方法"></a>handle方法</h3><p>可以感知到方法处理结果，<strong>并进行处理</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;Integer&gt; future = CompletableFuture.supplyAsync(() -&gt;<span class="number">4</span>/<span class="number">2</span>,</span><br><span class="line">        executor).</span><br><span class="line">        handle((result,exception)-&gt; &#123;</span><br><span class="line">                    <span class="keyword">if</span> (result != <span class="literal">null</span>) &#123;</span><br><span class="line">                        <span class="keyword">return</span> result * <span class="number">2</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (exception != <span class="literal">null</span>) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            );</span><br><span class="line">System.out.println(future.get());</span><br></pre></td></tr></table></figure>

<h3 id="线程串行化方法"><a href="#线程串行化方法" class="headerlink" title="线程串行化方法"></a>线程串行化方法</h3><p>例如：B需要等待A执行完毕才执行</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// thenRun 只要上一个任务执行完，就开始执行，无法获取上一步的执行结果，无返回值</span></span><br><span class="line"><span class="comment">// 没有Async表示和A共用一个线程，加了Async表示开启一个线程执行，异步执行</span></span><br><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">thenRun</span><span class="params">(Runnable action)</span>;</span><br><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">thenRunAsync</span><span class="params">(Runnable action)</span>;</span><br><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">thenRunAsync</span><span class="params">(Runnable action, Executor executor)</span>;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// thenAccept T代表上一步的返回结果，并进行处理，无返回值</span></span><br><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">thenAccept</span><span class="params">(Consumer&lt;? <span class="built_in">super</span> T&gt; action)</span>;</span><br><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">thenAcceptAsync</span><span class="params">(Consumer&lt;? <span class="built_in">super</span> T&gt; action)</span>;</span><br><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">thenAcceptAsync</span><span class="params">(Consumer&lt;? <span class="built_in">super</span> T&gt; action,Executor executor)</span>;</span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// B依赖A，获取A的结果并返回当前结果</span></span><br><span class="line"><span class="keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">thenApply</span><span class="params">(</span></span><br><span class="line"><span class="params">    Function&lt;? <span class="built_in">super</span> T,? extends U&gt; fn)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> uniApplyStage(<span class="literal">null</span>, fn);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">thenApplyAsync</span><span class="params">(</span></span><br><span class="line"><span class="params">    Function&lt;? <span class="built_in">super</span> T,? extends U&gt; fn)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> uniApplyStage(asyncPool, fn);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">thenApplyAsync</span><span class="params">(</span></span><br><span class="line"><span class="params">    Function&lt;? <span class="built_in">super</span> T,? extends U&gt; fn, Executor executor)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> uniApplyStage(screenExecutor(executor), fn);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>thenRun：无返回值，无法感知上一步结果</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture.</span><br><span class="line">        supplyAsync(()-&gt;<span class="string">&quot;黄宏涛&quot;</span>,executor).</span><br><span class="line">        <span class="comment">// thenRunAsync异步执行，无返回值，无法感知上一步结果</span></span><br><span class="line">        thenRunAsync(()-&gt;&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;黄宏涛&quot;</span>);</span><br><span class="line">        &#125;,executor);</span><br></pre></td></tr></table></figure>

<blockquote>
<p>thenAccept：无返回值，可以感知上一步结果</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture.</span><br><span class="line">        supplyAsync(()-&gt;<span class="string">&quot;黄宏涛&quot;</span>,executor).</span><br><span class="line">        <span class="comment">// 无返回值，可以感知上一步结果</span></span><br><span class="line">        thenAcceptAsync(t-&gt;&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;上一步的返回结果&quot;</span>+t);</span><br><span class="line">        &#125;,executor);</span><br></pre></td></tr></table></figure>

<blockquote>
<p>thenApply: 有返回值，可以感知上一步结果</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future = CompletableFuture.</span><br><span class="line">        supplyAsync(() -&gt; <span class="string">&quot;黄宏涛&quot;</span>, executor).</span><br><span class="line">        <span class="comment">// 有返回值，可以感知上一步结果</span></span><br><span class="line">         thenApplyAsync(t -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;上一步的结果：&quot;</span> + t);</span><br><span class="line">            <span class="keyword">return</span> t;</span><br><span class="line">        &#125;, executor);</span><br><span class="line">System.out.println(future.get());</span><br></pre></td></tr></table></figure>

<h3 id="两任务组合–都要完成"><a href="#两任务组合–都要完成" class="headerlink" title="两任务组合–都要完成"></a>两任务组合–都要完成</h3><p>runAfterBoth：组合两个future，不需要获取future的结果，需要两个future处理完成后处理该任务</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 合并两个异步任务</span></span><br><span class="line">CompletableFuture&lt;String&gt; future1 = CompletableFuture.</span><br><span class="line">        supplyAsync(() -&gt; <span class="string">&quot;黄宏涛1&quot;</span>, executor);</span><br><span class="line">CompletableFuture&lt;String&gt; future2 = CompletableFuture.</span><br><span class="line">        supplyAsync(() -&gt; <span class="string">&quot;黄宏涛2&quot;</span>, executor);</span><br><span class="line"></span><br><span class="line"><span class="comment">// runAfterBothAsync：两个执行完再执行这个，不能感知前两个的结果</span></span><br><span class="line"><span class="comment">/*future1.runAfterBothAsync(future2,()-&gt; &#123;</span></span><br><span class="line"><span class="comment">    System.out.println(&quot;任务三开始&quot;);</span></span><br><span class="line"><span class="comment">&#125;,executor);*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// thenAcceptBothAsync: 两个执行完再执行这个，可以感知前两个的结果</span></span><br><span class="line"><span class="comment">/*future1.thenAcceptBothAsync(future2,(f1,f2)-&gt;&#123;</span></span><br><span class="line"><span class="comment">    System.out.println(&quot;任务三开始之前的两个结果&quot;+f1+&quot; &quot;+f2);</span></span><br><span class="line"><span class="comment">&#125;,executor);*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// thenAcceptBothAsync: 两个执行完再执行这个，可以感知前两个的结果,并有返回结果</span></span><br><span class="line">future1.thenCombineAsync(future2,(f1,f2)-&gt;&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;任务三开始之前的两个结果&quot;</span>+f1+<span class="string">&quot; &quot;</span>+f2);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;任务三的结果&quot;</span>;</span><br><span class="line">&#125;,executor);</span><br></pre></td></tr></table></figure>

<h3 id="两任务组合–一个完成"><a href="#两任务组合–一个完成" class="headerlink" title="两任务组合–一个完成"></a>两任务组合–一个完成</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// runAfterEitherAsync两个任务，完成一个就执行任务三，不感知前面结果、且没有返回值</span></span><br><span class="line">future1.runAfterEitherAsync(future1,()-&gt;&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;任务三结果&quot;</span>);</span><br><span class="line">&#125;,executor);</span><br><span class="line"></span><br><span class="line"><span class="comment">// runAfterEitherAsync两个任务，完成一个就执行任务三，感知前面结果、且没有返回值</span></span><br><span class="line">future1.acceptEitherAsync(future2,(f)-&gt;&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;返回完成任务的返回结果&quot;</span>+f);</span><br><span class="line">&#125;,executor);</span><br><span class="line"></span><br><span class="line"><span class="comment">// applyToEitherAsync 两个任务，完成一个就执行任务三，感知前面结果、且有返回值</span></span><br><span class="line">future1.applyToEitherAsync(future2,(r)-&gt;&#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;返回已经完成的任务结果&quot;</span>+r);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;任务返回结果&quot;</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="多任务组合"><a href="#多任务组合" class="headerlink" title="多任务组合"></a>多任务组合</h3><ul>
<li>allOf等待所有任务完成</li>
<li>anyOf只要有一个任务完成</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;String&gt; future1 = CompletableFuture.</span><br><span class="line">        supplyAsync(() -&gt; <span class="string">&quot;商品图片信息&quot;</span>, executor);</span><br><span class="line">CompletableFuture&lt;String&gt; future2 = CompletableFuture.</span><br><span class="line">        supplyAsync(() -&gt; <span class="string">&quot;商品属性&quot;</span>, executor);</span><br><span class="line">CompletableFuture&lt;String&gt; future3 = CompletableFuture.</span><br><span class="line">        supplyAsync(() -&gt; <span class="string">&quot;商品介绍&quot;</span>, executor);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;Void&gt; allof = CompletableFuture.allOf(future1, future2, future3);</span><br><span class="line">allof.get();<span class="comment">// 阻塞式等待所有结果完成</span></span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;Object&gt; anyOf = CompletableFuture.anyOf(future1, future2, future3);</span><br><span class="line">anyOf.get();<span class="comment">// 返回成功执行的一个返回值</span></span><br></pre></td></tr></table></figure>





<h1 id="谷粒商城知识点："><a href="#谷粒商城知识点：" class="headerlink" title="谷粒商城知识点："></a>谷粒商城知识点：</h1><ol>
<li>拦截器</li>
<li>线程池+yml配置</li>
<li>异步编排</li>
<li>JSON格式转换</li>
<li>Fegin远程调用</li>
<li>Gateway网关</li>
<li>Nginx代理</li>
<li>Nacos注册中心</li>
<li>幂等性</li>
<li>分布式事务Seata</li>
<li>购物车实现</li>
<li>登录实现</li>
<li>库存解锁服务</li>
<li>搜索实现</li>
<li>秒杀实现</li>
<li>订单实现</li>
</ol>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E/" rel="tag"># 谷粒商城</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2023/10/30/SpringBoot/" rel="next" title="SpringBoot">
                <i class="fa fa-chevron-left"></i> SpringBoot
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2023/11/10/RabbitMQ/" rel="prev" title="RabbitMQ">
                RabbitMQ <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/avatar.png"
                alt="" />
            
              <p class="site-author-name" itemprop="name"></p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives">
              
                  <span class="site-state-item-count">10</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">6</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">8</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E"><span class="nav-number">1.</span> <span class="nav-text">谷粒商城</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%95%86%E5%9F%8E%E4%B8%9A%E5%8A%A1"><span class="nav-number">1.1.</span> <span class="nav-text">商城业务</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%95%86%E5%93%81%E4%B8%8A%E6%9E%B6"><span class="nav-number">1.1.1.</span> <span class="nav-text">商品上架</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%B0%86%E5%95%86%E5%93%81%E5%9C%A8es%E4%B8%AD%E5%AD%98%E5%82%A8"><span class="nav-number">1.1.1.1.</span> <span class="nav-text">将商品在es中存储</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BF%AE%E6%94%B9R%E5%AF%B9%E8%B1%A1%EF%BC%8C%E8%83%BD%E5%A4%9F%E8%87%AA%E5%8A%A8%E9%80%86%E8%BD%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="nav-number">1.1.1.2.</span> <span class="nav-text">修改R对象，能够自动逆转数据类型</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%B0%81%E8%A3%85SKU%E4%BF%A1%E6%81%AF"><span class="nav-number">1.1.1.3.</span> <span class="nav-text">封装SKU信息</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%90%91ES%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE"><span class="nav-number">1.1.1.4.</span> <span class="nav-text">向ES发送数据</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%A6%96%E9%A1%B5-%E4%B8%89%E7%BA%A7%E5%88%86%E7%B1%BB%E6%95%B0%E6%8D%AE"><span class="nav-number">1.1.2.</span> <span class="nav-text">首页-三级分类数据</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#nginx%E6%90%AD%E5%BB%BA%E5%9F%9F%E5%90%8D%E8%AE%BF%E9%97%AE%E7%8E%AF%E5%A2%83"><span class="nav-number">1.1.3.</span> <span class="nav-text">nginx搭建域名访问环境</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A3%80%E7%B4%A2%E6%9C%8D%E5%8A%A1"><span class="nav-number">1.1.4.</span> <span class="nav-text">检索服务</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%A3%80%E7%B4%A2%E5%8F%82%E6%95%B0"><span class="nav-number">1.1.4.1.</span> <span class="nav-text">检索参数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E5%B0%81%E8%A3%85"><span class="nav-number">1.1.4.2.</span> <span class="nav-text">请求参数封装</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%93%8D%E5%BA%94%E5%8F%82%E6%95%B0%E5%B0%81%E8%A3%85"><span class="nav-number">1.1.4.3.</span> <span class="nav-text">响应参数封装</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%A3%80%E7%B4%A2DSL%E6%B5%8B%E8%AF%95%E2%80%93ES%E6%9F%A5%E8%AF%A2"><span class="nav-number">1.1.4.4.</span> <span class="nav-text">检索DSL测试–ES查询</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%A3%80%E7%B4%A2DSL%E8%81%9A%E5%90%88%E5%88%86%E6%9E%90"><span class="nav-number">1.1.4.5.</span> <span class="nav-text">检索DSL聚合分析</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#SearchRequest%E6%A3%80%E7%B4%A2%E8%AF%B7%E6%B1%82"><span class="nav-number">1.1.4.6.</span> <span class="nav-text">SearchRequest检索请求</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#SearchResult%E5%B0%81%E8%A3%85%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE"><span class="nav-number">1.1.4.7.</span> <span class="nav-text">SearchResult封装返回数据</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85"><span class="nav-number">1.1.5.</span> <span class="nav-text">商品详情</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E7%9A%84%E5%BC%82%E6%AD%A5%E7%BC%96%E6%8E%92"><span class="nav-number">1.1.5.1.</span> <span class="nav-text">商品详情的异步编排</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%AE%A4%E8%AF%81%E6%9C%8D%E5%8A%A1"><span class="nav-number">1.1.6.</span> <span class="nav-text">认证服务</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#UserRegisVo%E6%8E%A5%E6%94%B6%E6%B3%A8%E5%86%8C%E4%BF%A1%E6%81%AF%E7%9A%84%E5%AF%B9%E8%B1%A1"><span class="nav-number">1.1.6.1.</span> <span class="nav-text">UserRegisVo接收注册信息的对象</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86MD5%E6%88%96MD5%E7%9B%90%E5%80%BC%E5%8A%A0%E5%AF%86"><span class="nav-number">1.1.6.2.</span> <span class="nav-text">密码加密MD5或MD5盐值加密</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#BCryptPasswordEncoder%E5%AF%86%E7%A0%81%E5%8A%A0%E5%AF%86%E5%99%A8"><span class="nav-number">1.1.6.3.</span> <span class="nav-text">BCryptPasswordEncoder密码加密器</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%A4%BE%E4%BA%A4%E7%99%BB%E5%BD%95"><span class="nav-number">1.1.7.</span> <span class="nav-text">社交登录</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#OAuth2-0"><span class="nav-number">1.1.7.1.</span> <span class="nav-text">OAuth2.0</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%B8%8D%E5%90%8C%E5%9F%9F%E5%90%8Dsession%E4%BF%A1%E6%81%AF%E4%B8%8D%E5%85%B1%E4%BA%AB"><span class="nav-number">1.1.7.2.</span> <span class="nav-text">不同域名session信息不共享</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#SpringSession%E8%A7%A3%E5%86%B3%EF%BC%8C%E4%B8%8D%E5%90%8C%E5%9F%9F%E5%90%8D%E7%9A%84Session%E5%85%B1%E4%BA%AB"><span class="nav-number">1.1.7.3.</span> <span class="nav-text">SpringSession解决，不同域名的Session共享</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#SpringSession%E5%8E%9F%E7%90%86"><span class="nav-number">1.1.7.4.</span> <span class="nav-text">SpringSession原理</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95"><span class="nav-number">1.1.8.</span> <span class="nav-text">单点登录</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B4%AD%E7%89%A9%E8%BD%A6"><span class="nav-number">1.1.9.</span> <span class="nav-text">购物车</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B%E5%88%86%E6%9E%90"><span class="nav-number">1.1.9.1.</span> <span class="nav-text">数据模型分析</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#ThreadLocal%E5%90%8C%E4%B8%80%E4%B8%AA%E7%BA%BF%E7%A8%8B%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE"><span class="nav-number">1.1.9.2.</span> <span class="nav-text">ThreadLocal同一个线程共享数据</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%B7%BB%E5%8A%A0%E5%88%B0%E8%B4%AD%E7%89%A9%E8%BD%A6"><span class="nav-number">1.1.9.3.</span> <span class="nav-text">添加到购物车</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%81%BF%E5%85%8D%E9%A1%B5%E9%9D%A2%E5%88%B7%E6%96%B0%EF%BC%8C%E5%95%86%E5%93%81%E6%95%B0%E9%87%8F%E4%B8%80%E7%9B%B4%E5%A2%9E%E5%8A%A0RedirectAttributes"><span class="nav-number">1.1.9.4.</span> <span class="nav-text">避免页面刷新，商品数量一直增加RedirectAttributes</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%8E%B7%E5%8F%96-%E5%90%88%E5%B9%B6%E8%B4%AD%E7%89%A9%E8%BD%A6"><span class="nav-number">1.1.9.5.</span> <span class="nav-text">获取-合并购物车</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%AE%A2%E5%8D%95%E6%9C%8D%E5%8A%A1"><span class="nav-number">1.1.10.</span> <span class="nav-text">订单服务</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%AE%A2%E5%8D%95%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5"><span class="nav-number">1.1.10.1.</span> <span class="nav-text">订单基本概念</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Fegin%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E4%B8%A2%E5%A4%B1%E8%AF%B7%E6%B1%82%E5%A4%B4"><span class="nav-number">1.1.10.2.</span> <span class="nav-text">Fegin远程调用丢失请求头</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Fegin%E5%BC%82%E6%AD%A5%E6%83%85%E5%86%B5%E4%B8%8B%E4%B8%A2%E5%A4%B1%E4%B8%8A%E4%B8%8B%E6%96%87"><span class="nav-number">1.1.10.3.</span> <span class="nav-text">Fegin异步情况下丢失上下文</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%B9%82%E7%AD%89%E6%80%A7"><span class="nav-number">1.1.10.4.</span> <span class="nav-text">幂等性</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%BB%B6%E6%97%B6%E9%98%9F%E5%88%97%EF%BC%88%E5%AE%9E%E7%8E%B0%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%EF%BC%89"><span class="nav-number">1.1.10.5.</span> <span class="nav-text">延时队列（实现定时任务）</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%BA%93%E5%AD%98%E8%A7%A3%E9%94%81%E6%9C%8D%E5%8A%A1"><span class="nav-number">1.1.10.6.</span> <span class="nav-text">库存解锁服务</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%BA%93%E5%AD%98%E6%9C%8D%E5%8A%A1RabbitMQ%E9%85%8D%E7%BD%AE"><span class="nav-number">1.1.10.6.1.</span> <span class="nav-text">库存服务RabbitMQ配置</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E8%AE%A2%E5%8D%95%E6%9C%8D%E5%8A%A1RabbitMQ%E9%85%8D%E7%BD%AE"><span class="nav-number">1.1.10.6.2.</span> <span class="nav-text">订单服务RabbitMQ配置</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E8%AE%A2%E5%8D%95"><span class="nav-number">1.1.10.6.3.</span> <span class="nav-text">创建订单</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E9%94%81%E5%BA%93%E5%AD%98"><span class="nav-number">1.1.10.6.4.</span> <span class="nav-text">锁库存</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E8%A7%A3%E9%94%81%E5%BA%93%E5%AD%98"><span class="nav-number">1.1.10.6.5.</span> <span class="nav-text">解锁库存</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AE%9A%E6%97%B6%E5%85%B3%E5%8D%95%E5%8A%9F%E8%83%BD"><span class="nav-number">1.1.10.7.</span> <span class="nav-text">定时关单功能</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E8%AE%A2%E5%8D%95%E9%87%8A%E6%94%BE-%E5%BA%93%E5%AD%98%E8%A7%A3%E9%94%81"><span class="nav-number">1.1.10.7.1.</span> <span class="nav-text">订单释放&amp;库存解锁</span></a></li></ol></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E6%9C%8D%E5%8A%A1"><span class="nav-number">1.1.11.</span> <span class="nav-text">秒杀服务</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E7%89%B9%E7%82%B9%EF%BC%9A"><span class="nav-number">1.1.11.1.</span> <span class="nav-text">秒杀特点：</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%99%90%E6%B5%81%E6%96%B9%E5%BC%8F"><span class="nav-number">1.1.11.2.</span> <span class="nav-text">限流方式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1"><span class="nav-number">1.1.11.3.</span> <span class="nav-text">定时任务</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#SpringBoot%E6%95%B4%E5%90%88%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1"><span class="nav-number">1.1.11.3.1.</span> <span class="nav-text">SpringBoot整合定时任务</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%9F%A5%E8%AF%A2%E6%9C%80%E8%BF%91%E4%B8%89%E5%A4%A9%E7%9A%84%E6%B4%BB%E5%8A%A8"><span class="nav-number">1.1.11.4.</span> <span class="nav-text">查询最近三天的活动</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E5%95%86%E5%93%81%E4%B8%8A%E6%9E%B6"><span class="nav-number">1.1.11.5.</span> <span class="nav-text">秒杀商品上架</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E5%AE%9A%E6%97%B6%E8%AE%BE%E7%BD%AE"><span class="nav-number">1.1.11.5.1.</span> <span class="nav-text">秒杀定时设置</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%B9%82%E7%AD%89%E6%80%A7%E8%AE%BE%E8%AE%A1"><span class="nav-number">1.1.11.5.2.</span> <span class="nav-text">幂等性设计</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E6%9F%A5%E8%AF%A2%E7%A7%92%E6%9D%80%E5%95%86%E5%93%81"><span class="nav-number">1.1.11.5.3.</span> <span class="nav-text">查询秒杀商品</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1"><span class="nav-number">1.1.11.5.4.</span> <span class="nav-text">秒杀系统设计</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%99%BB%E5%BD%95%E6%A3%80%E6%9F%A5"><span class="nav-number">1.1.11.5.5.</span> <span class="nav-text">登录检查</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E6%B5%81%E7%A8%8B"><span class="nav-number">1.1.11.5.6.</span> <span class="nav-text">秒杀流程</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%A7%92%E6%9D%80%E4%BB%A3%E7%A0%81"><span class="nav-number">1.1.11.6.</span> <span class="nav-text">秒杀代码</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#JSON%E5%BA%8F%E5%88%97%E5%8C%96%E8%AE%BE%E7%BD%AE"><span class="nav-number">1.1.11.6.1.</span> <span class="nav-text">JSON序列化设置</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#Redisson%E5%88%86%E5%B8%83%E5%BC%8F%E8%AE%BE%E7%BD%AE"><span class="nav-number">1.1.11.6.2.</span> <span class="nav-text">Redisson分布式设置</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#Controller%E8%AF%B7%E6%B1%82%E8%AE%BE%E7%BD%AE"><span class="nav-number">1.1.11.6.3.</span> <span class="nav-text">Controller请求设置</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#kill%E6%96%B9%E6%B3%95%EF%BC%8C%E5%90%88%E6%B3%95%E6%80%A7%E6%A0%A1%E9%AA%8C"><span class="nav-number">1.1.11.6.4.</span> <span class="nav-text">kill方法，合法性校验</span></a></li></ol></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Sentinel%E9%AB%98%E5%B9%B6%E5%8F%91%E2%80%93%E4%BF%9D%E8%AF%81%E7%A8%B3"><span class="nav-number">1.1.12.</span> <span class="nav-text">Sentinel高并发–保证稳</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%AE%80%E4%BB%8B"><span class="nav-number">1.1.12.1.</span> <span class="nav-text">简介</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E9%99%90%E6%B5%81%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7"><span class="nav-number">1.1.12.1.1.</span> <span class="nav-text">限流熔断降级</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Sentinel%E4%BD%BF%E7%94%A8"><span class="nav-number">1.1.12.2.</span> <span class="nav-text">Sentinel使用</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Sentinel%E6%95%B4%E5%90%88SpringBoot"><span class="nav-number">1.1.12.3.</span> <span class="nav-text">Sentinel整合SpringBoot</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Sentinel%E7%86%94%E6%96%AD%E5%92%8C%E9%99%8D%E7%BA%A7%E2%80%94-%E6%95%B4%E5%90%88Feign"><span class="nav-number">1.1.12.4.</span> <span class="nav-text">Sentinel熔断和降级—-整合Feign</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E8%B0%83%E7%94%A8%E6%96%B9%E7%9A%84%E7%86%94%E6%96%AD%E4%BF%9D%E6%8A%A4"><span class="nav-number">1.1.12.4.1.</span> <span class="nav-text">调用方的熔断保护</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%9C%A8%E9%A1%B5%E9%9D%A2%E6%89%8B%E5%8A%A8%E6%8C%87%E5%AE%9A%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E7%9A%84%E9%99%8D%E7%BA%A7%E7%AD%96%E7%95%A5"><span class="nav-number">1.1.12.4.2.</span> <span class="nav-text">在页面手动指定远程服务的降级策略</span></a></li></ol></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8F%97%E4%BF%9D%E6%8A%A4%E8%B5%84%E6%BA%90"><span class="nav-number">1.1.13.</span> <span class="nav-text">自定义受保护资源</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%BD%91%E5%85%B3%E6%B5%81%E6%8E%A7"><span class="nav-number">1.1.13.1.</span> <span class="nav-text">网关流控</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1"><span class="nav-number">1.2.</span> <span class="nav-text">本地事务</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1-1"><span class="nav-number">1.2.1.</span> <span class="nav-text">本地事务</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%80%A7%E8%B4%A8"><span class="nav-number">1.2.1.1.</span> <span class="nav-text">事务的基本性质</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB"><span class="nav-number">1.2.2.</span> <span class="nav-text">事务隔离级别</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BA%8B%E5%8A%A1%E7%9A%84%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA"><span class="nav-number">1.2.3.</span> <span class="nav-text">事务的传播行为</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%AC%E5%9C%B0%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%9D%91"><span class="nav-number">1.2.4.</span> <span class="nav-text">本地事务的坑</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1"><span class="nav-number">1.3.</span> <span class="nav-text">分布式事务</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#CAP%E5%AE%9A%E7%90%86"><span class="nav-number">1.3.1.</span> <span class="nav-text">CAP定理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#BASE%E7%90%86%E8%AE%BA"><span class="nav-number">1.3.2.</span> <span class="nav-text">BASE理论</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><span class="nav-number">1.3.3.</span> <span class="nav-text">分布式事务解决方案</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Seata%EF%BC%882PC%E6%A8%A1%E5%BC%8F%EF%BC%89"><span class="nav-number">1.3.4.</span> <span class="nav-text">Seata（2PC模式）</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95"><span class="nav-number">1.4.</span> <span class="nav-text">压力测试</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Address-Already-in-use%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3"><span class="nav-number">1.4.1.</span> <span class="nav-text">Address Already in use问题解决</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A0%86%E5%86%85%E5%AD%98%E5%92%8C%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="nav-number">1.4.2.</span> <span class="nav-text">堆内存和垃圾回收</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7"><span class="nav-number">1.4.2.1.</span> <span class="nav-text">性能监控</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%AF%B9%E6%80%A7%E8%83%BD%E7%9A%84%E5%BD%B1%E5%93%8D"><span class="nav-number">1.4.3.</span> <span class="nav-text">中间件对性能的影响</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AE%80%E5%8D%95%E7%9A%84%E4%BC%98%E5%8C%96%E5%90%9E%E5%90%90%E9%87%8F%E6%B5%8B%E8%AF%95"><span class="nav-number">1.4.4.</span> <span class="nav-text">简单的优化吞吐量测试</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BC%98%E5%8C%96%E4%B8%89%E7%BA%A7%E5%88%86%E7%B1%BB%E6%95%B0%E6%8D%AE"><span class="nav-number">1.4.5.</span> <span class="nav-text">优化三级分类数据</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E5%92%8C%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="nav-number">1.5.</span> <span class="nav-text">缓存和分布式锁</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BC%93%E5%AD%98"><span class="nav-number">1.5.1.</span> <span class="nav-text">缓存</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%95%B4%E5%90%88Redis"><span class="nav-number">1.5.2.</span> <span class="nav-text">整合Redis</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8%E7%BC%93%E5%AD%98%E4%BC%98%E5%8C%96-%E4%B8%89%E7%BA%A7%E5%88%86%E7%B1%BB%E6%95%B0%E6%8D%AE%E6%9F%A5%E8%AF%A2"><span class="nav-number">1.5.3.</span> <span class="nav-text">使用缓存优化-三级分类数据查询</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%EF%BC%88%E7%89%88%E6%9C%AC%E5%B7%B2%E7%BB%8F%E4%BF%AE%E5%A4%8D%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%EF%BC%89"><span class="nav-number">1.5.4.</span> <span class="nav-text">压力测试内存溢出（版本已经修复这个问题）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF%E3%80%81%E7%A9%BF%E9%80%8F"><span class="nav-number">1.5.5.</span> <span class="nav-text">缓存击穿、穿透</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8%E5%8A%A0%E9%94%81%E8%A7%A3%E5%86%B3%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF"><span class="nav-number">1.5.6.</span> <span class="nav-text">使用加锁解决缓存击穿</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%AC%E5%9C%B0%E9%94%81%E5%9C%A8%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%8B%E7%9A%84%E8%A1%A8%E7%8E%B0"><span class="nav-number">1.5.7.</span> <span class="nav-text">本地锁在分布式下的表现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%8E%9F%E7%90%86"><span class="nav-number">1.5.8.</span> <span class="nav-text">分布式锁原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Redisson%E7%AE%80%E4%BB%8B-Redis%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81"><span class="nav-number">1.5.9.</span> <span class="nav-text">Redisson简介-Redis分布式锁</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Lock%E9%94%81%E6%B5%8B%E8%AF%95"><span class="nav-number">1.5.9.1.</span> <span class="nav-text">Lock锁测试</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%AF%BB%E5%86%99%E9%94%81"><span class="nav-number">1.5.9.2.</span> <span class="nav-text">读写锁</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%97%AD%E9%94%81"><span class="nav-number">1.5.9.3.</span> <span class="nav-text">闭锁</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BF%A1%E5%8F%B7%E9%87%8FSemaphore"><span class="nav-number">1.5.9.4.</span> <span class="nav-text">信号量Semaphore</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%88%E7%BC%93%E5%AD%98%E5%92%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%80%E8%87%B4%E6%80%A7%EF%BC%89%E8%A7%A3%E5%86%B3"><span class="nav-number">1.5.9.5.</span> <span class="nav-text">缓存一致性（缓存和数据库一致性）解决</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Spring-Cache"><span class="nav-number">1.5.10.</span> <span class="nav-text">Spring Cache</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%AE%80%E4%BB%8B-1"><span class="nav-number">1.5.10.1.</span> <span class="nav-text">简介</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%95%B4%E5%90%88Spring-Cache"><span class="nav-number">1.5.10.2.</span> <span class="nav-text">整合Spring-Cache</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Cacheable%E6%B3%A8%E8%A7%A3"><span class="nav-number">1.5.10.3.</span> <span class="nav-text">@Cacheable注解</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BC%93%E5%AD%98%E9%85%8D%E7%BD%AE"><span class="nav-number">1.5.10.4.</span> <span class="nav-text">自定义缓存配置</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#JSON%E5%BA%8F%E5%88%97%E5%8C%96"><span class="nav-number">1.5.10.5.</span> <span class="nav-text">JSON序列化</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#CacheEvict"><span class="nav-number">1.5.10.6.</span> <span class="nav-text">@CacheEvict</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Caching"><span class="nav-number">1.5.10.7.</span> <span class="nav-text">@Caching</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#SpringCache%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E4%B8%8D%E8%B6%B3"><span class="nav-number">1.5.10.8.</span> <span class="nav-text">SpringCache的原理与不足</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%BC%82%E6%AD%A5-%E7%BA%BF%E7%A8%8B"><span class="nav-number">2.</span> <span class="nav-text">异步-线程</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%9D%E5%A7%8B%E5%8C%96%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%9B%9B%E7%A7%8D%E6%96%B9%E5%BC%8F"><span class="nav-number">2.1.</span> <span class="nav-text">初始化线程的四种方式</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0"><span class="nav-number">2.2.</span> <span class="nav-text">线程池</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8ThreadPoolExecutor%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E6%B1%A0"><span class="nav-number">2.2.1.</span> <span class="nav-text">使用ThreadPoolExecutor创建线程池</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8Executors%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E6%B1%A0"><span class="nav-number">2.2.2.</span> <span class="nav-text">使用Executors创建线程池</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%BC%82%E6%AD%A5%E7%BC%96%E6%8E%92CompletableFuture"><span class="nav-number">2.3.</span> <span class="nav-text">异步编排CompletableFuture</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E5%BC%82%E6%AD%A5%E5%AF%B9%E8%B1%A1"><span class="nav-number">2.3.1.</span> <span class="nav-text">创建异步对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%AE%A1%E7%AE%97%E5%AE%8C%E6%88%90%E6%97%B6%E5%9B%9E%E8%B0%83%E6%96%B9%E6%B3%95"><span class="nav-number">2.3.2.</span> <span class="nav-text">计算完成时回调方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#handle%E6%96%B9%E6%B3%95"><span class="nav-number">2.3.3.</span> <span class="nav-text">handle方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E4%B8%B2%E8%A1%8C%E5%8C%96%E6%96%B9%E6%B3%95"><span class="nav-number">2.3.4.</span> <span class="nav-text">线程串行化方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%A4%E4%BB%BB%E5%8A%A1%E7%BB%84%E5%90%88%E2%80%93%E9%83%BD%E8%A6%81%E5%AE%8C%E6%88%90"><span class="nav-number">2.3.5.</span> <span class="nav-text">两任务组合–都要完成</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%B8%A4%E4%BB%BB%E5%8A%A1%E7%BB%84%E5%90%88%E2%80%93%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%88%90"><span class="nav-number">2.3.6.</span> <span class="nav-text">两任务组合–一个完成</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A4%9A%E4%BB%BB%E5%8A%A1%E7%BB%84%E5%90%88"><span class="nav-number">2.3.7.</span> <span class="nav-text">多任务组合</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%B0%B7%E7%B2%92%E5%95%86%E5%9F%8E%E7%9F%A5%E8%AF%86%E7%82%B9%EF%BC%9A"><span class="nav-number">3.</span> <span class="nav-text">谷粒商城知识点：</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">黄宏涛</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  


  











  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  

  
  
    <script type="text/javascript" src="/lib/canvas-nest/canvas-nest.min.js"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  

  

  
  

  

  

  

</body>
</html>
